下面通过一个简单的Console Application演示Type Innitializer的执行顺序。希望大家各抒己见,对于实验的结果给出一个圆满的解释,同时希望读者从中理解到更多关于编译、关于CLR一些被我们忽略的细节。
代码如下,在类Foo中定义了两个static成员:静态字段Field和静态方法GetString,Field通过于Inline的方式通过调用GetString进行初始化。在Main()中仅仅两行代码:Console.WriteLine("Start ...");Foo.GetString("Manually invoke the static GetString() method!");
1: using System;
<!--CRLF-->
2: namespace Artech.TypeInitializerDemo
<!--CRLF-->
3: {
<!--CRLF-->
4: class Program
<!--CRLF-->
5: {
<!--CRLF-->
6: static void Main()
<!--CRLF-->
7: {
<!--CRLF-->
8: Console.WriteLine("Start ...");
<!--CRLF-->
9: Foo.GetString("Manually invoke the static GetString() method!");
<!--CRLF-->
10: }
<!--CRLF-->
11: }
<!--CRLF-->
12:
<!--CRLF-->
13: class Foo
<!--CRLF-->
14: {
<!--CRLF-->
15: public static string Field = GetString("Initialize the static field!");
<!--CRLF-->
16:
<!--CRLF-->
17: public static string GetString(string s)
<!--CRLF-->
18: {
<!--CRLF-->
19: Console.WriteLine(s);
<!--CRLF-->
20: return s;
<!--CRLF-->
21: }
<!--CRLF-->
22: }
<!--CRLF-->
23: }
<!--CRLF-->
对于结果,我想很多人都能够猜得到,如果在显示调用GetString()之前,需要完成静态成员的初始化,所以最终的输出结果如下图所示:
然后我们在Main()种多加一行代码:string field = Foo.Field; 也就是获取Foo的静态字段:
1: static void Main()
<!--CRLF-->
2: {
<!--CRLF-->
3: Console.WriteLine("Start ...");
<!--CRLF-->
4: Foo.GetString("Manually invoke the static GetString() method!");
<!--CRLF-->
5: string field = Foo.Field;
<!--CRLF-->
6: }
<!--CRLF-->
最终的输出结果就和上面不一样了,静态字段的初始化工作居然提前了(在Console.WriteLine("Start ...");之前执行)
“神奇”的事情还没有结束,如果我们在Foo中加上一个静态构造函数,其中不执行任何的操作:
1: class Foo
<!--CRLF-->
2: {
<!--CRLF-->
3: public static string Field = GetString("Initialize the static field!");
<!--CRLF-->
4:
<!--CRLF-->
5: static Foo()
<!--CRLF-->
6: { }
<!--CRLF-->
7:
<!--CRLF-->
8: public static string GetString(string s)
<!--CRLF-->
9: {
<!--CRLF-->
10: Console.WriteLine(s);
<!--CRLF-->
11: return s;
<!--CRLF-->
12: }
<!--CRLF-->
13: }
<!--CRLF-->
再来看看现在执行结果,又和先前的一样的了。
我先不做任何评论(因为我也不太确定我的认识就是正确的),看看大家对此有什么看法。
再添加另一个static constructor的例子,较之上面一个要简单点。在Bar继承自基类Foo,在Foo和Bar均定义了静态构造函数。静态方法DoSomething()定义在Foo中,在Main()中却通过Bar.DoSomething();进行调用。
1: using System;
<!--CRLF-->
2: namespace Artech.TypeInitializerDemo
<!--CRLF-->
3: {
<!--CRLF-->
4: class Program
<!--CRLF-->
5: {
<!--CRLF-->
6: static void Main()
<!--CRLF-->
7: {
<!--CRLF-->
8: Bar.DoSomething();
<!--CRLF-->
9: }
<!--CRLF-->
10: }
<!--CRLF-->
11:
<!--CRLF-->
12: public abstract class Foo
<!--CRLF-->
13: {
<!--CRLF-->
14: static Foo()
<!--CRLF-->
15: {
<!--CRLF-->
16: Console.WriteLine("static Foo() is invoked");
<!--CRLF-->
17: }
<!--CRLF-->
18:
<!--CRLF-->
19: public static void DoSomething()
<!--CRLF-->
20: {
<!--CRLF-->
21: Console.WriteLine("Done ...");
<!--CRLF-->
22: }
<!--CRLF-->
23: }
<!--CRLF-->
24:
<!--CRLF-->
25: public class Bar : Foo
<!--CRLF-->
26: {
<!--CRLF-->
27: static Bar()
<!--CRLF-->
28: {
<!--CRLF-->
29: Console.WriteLine("static Bar() is invoked");
<!--CRLF-->
30: }
<!--CRLF-->
31: }
<!--CRLF-->
32: }
<!--CRLF-->
下面是输出结果,可见虽然通过Bar调用了静态方法DoSomething,但是Bar的静态构造函数没有被执行。这个很好理解,因为Something是定义在基类Foo上,Bar.DoSomething()本质上相当于Foo.DoSomething()。所以只会调用Foo的静态构造函数。
个人觉得,这是编译器值得改进的地方,既然静态方法是基于类型的方法,只能通过定义了该静态方法的那个类型进行调用,至于其他的类,哪怕是该类的子类,都不能调用该方法。编译器不应该让这样的代码通过编译。不知道读者的意见如何。
分享到:
相关推荐
解决Anolis(龙蜥),解决System.TypeInitializationException: The type initializer for ‘Gdip’ threw an exception.
dry-initializer, 使用参数和选项构建类初始值设定项的DSL 干初始值设定项 使用参数和选项构建类初始值设定项的DSL 。安装将此行添加到你的应用程序的Gemfile中:gem 'dry-initializer'然后执行:$ bundle
内网中连接不上https://start.spring.io,搭建\https://start.spring.io一样的服务器。 initializr-service-0.7.0.BUILD-SNAPSHOT.jar
initializer-list分析
然而,由于AVR-GCC的轻量级特性,它可能不直接支持某些C++11及更高版本的高级特性,如lambda表达式和`std::initializer_list`。 Lambda表达式是C++11引入的一个强大特性,它允许我们在代码中定义匿名函数,常用于...
从压缩包子文件的文件名称列表 "dodoo-initializer-0.5.1" 来看,解压后应该会得到一个名为 "dodoo-initializer-0.5.1" 的目录,其中包含了该库的源代码、元数据文件(如setup.py和MANIFEST.in)、测试代码、文档...
Aspose.Words是一款著名的文档处理库,用于在.NET和.NET Core平台上创建、编辑和操作Microsoft Word文档。在Aspose.Words 18.7版本中,引入了SkiaSharp的集成,这是一个强大的2D图形处理库,为解决特定的错误和问题...
### 关于Vs2015 EF 连接Oracle 出现OracleInternal.Common.ConfigBaseClass的解决办法 在使用Visual Studio 2015 (简称Vs2015)结合Entity Framework (简称EF)进行开发时,尝试连接Oracle数据库可能会遇到一个常见的...
java web项目部署到CentOS后,导致验证码不能正常显示,找了很多方法翻了很多资料,最终得以解决。
invalid initializer(解决方案).md
Spring Boot Initializer是Spring Boot项目启动的一个重要组成部分,它简化了构建基于Spring的应用程序的过程。在深入了解ApplicationContextInitializer之前,我们先来谈谈Spring Boot的核心概念。 Spring Boot...
`iter_iter_initializer_list.pass.c` 文件名暗示了它可能是一个关于迭代器和初始化列表的C++测试用例。在C++中,迭代器是一种指向容器内元素的对象,而初始化列表则用于构造对象时提供初始值。这部分代码可能涉及...
《Python库lztools.initializer-1.0.12-py3.7.egg详解》 在Python的世界里,库是开发者的重要工具,它们提供了丰富的功能,帮助我们更高效地编写代码。今天我们要讨论的“lztools.initializer-1.0.12-py3.7.egg”...
由于https://start.spring.io/ 网站访问不稳定或者超时现象,本次搭建0.12.0 最新 版本 ,springboot 达到 2.7.0 相关组件最新 启动方法 java -jar initializr-service-custom-2.6.3.jar 已经打包的jar无须下载源码...
Spring Initializer(Spring 版 + Aliyun 版) JDK Require:JDK 17 启动命令:java -jar xxx.jar 1、Spring 版:官网 https://start.spring.io/ -> 本地 http://localhost:8080/ 2、Aliyun 版:官网 ...
本文将深入探讨如何使用C#实现高效且不失真的图片上传和缩略图生成方法,主要关注`SaveImage`函数及其优化策略。 首先,让我们了解`SaveImage`函数的基本概念。在C#中,我们通常会使用System.Drawing命名空间中的...
系统找不到指定的文件。 (Microsoft.SqlServer.Dmf) 解决办法:将Microsoft.SqlServer.Diagnostics.STrace.dll复制到C:\Program Files\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE这个目录即可。祝...
在使用Emgu.CV库进行计算机视觉开发的过程中,可能会遇到标题中提到的问题——“Emgu.CV.CvInvoke”的类型初始值设定项引发异常。这通常是因为缺少必要的依赖库或者配置不正确导致的。Emgu.CV是C#的一个OpenCV封装库...
Laravel Initializer使您能够声明这些进程并通过简单的app:install和app:update artisan命令运行它们,这些命令根据当前环境运行预定义的操作链。 此外, app:update命令还可以简化Forge,Envoy.blade.php,...
[..]] :warning: 我不能再推荐这种方法了。 相反,我强烈建议在C#9中使用新的模块初始化程序功能。 旁注:这些项目是一部分,但已被外包,因为它们参与了Azure DevOps的Windows Defender并导致随机生成失败。关于该...