public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Singleton()
{ }
private Singleton()
{ }
public static Singleton Instance { get { return instance; } }
}
using System;
class A {
public A() { Console.WriteLine("A"); }
}
class B {
public B() { Console.WriteLine("B"); }
}
public class Test
{
static readonly A a;
static readonly B b;
public static void Main()
{
}
static Test()
{
b = new B();
Console.WriteLine("-");
a = new A();
}
}
在 C# 中有几种初始化字段的方法:
在广告的地方
在构造函数中
这是一个天真的例子:
从鸟瞰图来看,就地初始化相当简单:用于初始化字段的表达式被传递给适当的构造函数——一个用于实例字段的实例构造函数和一个用于静态字段的静态构造函数。
这里有必要说一下为什么我们有两个构造函数。实例构造函数是某种辅助函数,旨在初始化正在创建的对象的实例。例如,我们可以说任何有效的对象都必须具有某些行为(不变量),而构造函数是必须提供它的特殊函数。任何东西都可以是不变的:从某些字段不为零的事实开始,以更复杂的规则结束,例如,字段
дебет和кредит等于 0。除了对象不变量之外,还有类型不变量:即 有些条件不是针对特定对象,而是针对整个类型。类型的行为是使用静态成员来表达的,这意味着类型的“不变量”是静态变量的有效状态,其有效性的责任由静态构造函数提供。
与对象构造函数不同,用户不调用类型构造函数。相反,它在第一次访问该类型时由 CLR 调用(我们将跳过确切的规则)。
运行时行为存在细微差别,可以区分在声明站点使用字段初始值设定项与在构造函数中初始化这些相同的字段。静态和实例字段初始化器是语法糖,但重要的是要知道是哪一个!
实例字段和构造函数的区别
C# 编译器将所有实例字段初始值设定项移至实例构造函数。但这里的主要问题是究竟在哪里。
通常,实例构造函数如下所示:
这个算法有两个重要的结果。首先,实例字段初始化代码不只是放在构造函数中,而是放在构造函数的最开始,在调用基类构造函数之前,其次,它会被复制到所有实例构造函数中。
第一句话非常重要(是的,这可以在面试时问到,在实际应用中可能会有用)。例如,如果有人决定在基类构造函数中调用虚方法,那么有些字段会被初始化,有些则不会。很容易猜到在声明处初始化的字段已经有效,其他字段将包含默认值。
是的,是的,是的,在基类构造函数中调用虚方法是不好的,但实际上它发生了,你需要了解在这种情况下运行时会发生什么。
静态字段和构造函数的区别
使用静态构造函数,事情会更复杂,同时也更容易。在继承方面,静态字段的初始化方式与调用基类的静态构造函数无关。没门。通常,调用静态构造函数的过程与调用实例构造函数的过程不同。比如创建继承人的实例时,先调用继承人的静态构造函数,再调用基类的静态构造函数。而如果继承人的静态方法在抽动,那么根本不会自动调用基类的静态构造函数(只有当继承人的静态方法以某种方式拉动基类时才会调用)。
但是困难在于什么时候调用静态构造函数。
正如@Qwertiy 已经写过的,类中是否存在静态构造函数会影响何时调用此构造函数。静态构造函数的存在导致生成一个奇怪的特殊标志,然后它将告诉 CLR 您可以更自由地调用静态构造函数的时间,现在可以不在调用之前执行此操作第一次调用,但是,例如,在调用发生此调用的方法之前。
这可能会影响应用程序的效率,因为现在假设第一次调用是在从 0 到 1000 的循环中,检查将进行一次,而不是数千次。
结论
字段初始化器(静态的和非静态的)是糖,但如果你不明白它的过度使用会导致什么,它可能会很苦。
我通常使用以下经验法则。例如字段:您需要使用构造函数参数初始化字段 - (无选项)使用构造函数;否则,一个字段启动器。对于静态字段:在绝大多数情况下,我使用初始化程序。如果有很多代码,那么我会突出显示该方法。
如果我需要使用静态构造函数来设置初始化顺序或更改类型初始化的语义,那么我会添加一个巨大的注释,说明为什么需要非常小心地处理这段代码。
如果我需要为实例字段使用初始化程序以便在调用基类构造函数之前进行初始化,那么我将重构代码以便不需要这样做。例如,我突出了一个工厂方法。如果确实需要这种行为,那么这里需要一个两页的评论,解释为什么需要它以及为什么其他选项不合适。
调用静态构造函数有两个保证:
为了让 CLR 使用“宽松”模型,类型必须标有 BeforeFieldInit 标志。对于 C# 语言,选择是由显式静态构造函数的存在与否决定的:如果有显式静态构造函数,则使用基本类型初始化模型,如果没有静态构造函数,则使用宽松模型。
实践表明,在大多数情况下,在没有显式构造函数的情况下,JIT 编译器会在调用使用该变量的方法之前立即调用静态变量初始化程序。
如果您使用 Singleton 类的静态构造函数,那么行为将正是大多数开发人员所期望的 - 字段初始值设定项将在第一次调用之前不会被调用。
来源: http:
//sergeyteplyakov.blogspot.ru/2013/07/blog-post.html
http://sergeyteplyakov.blogspot.ru/2011/08/blog-post.html
http://sergeyteplyakov.blogspot.ru/ 2013/06/blog-post_28.html
静态构造函数的本质是它仅在创建指定类型的第一个元素之前被调用一次。当然,您可以在每次创建实例时都进行初始化并进行额外的检查,但是为什么呢?
如果静态字段的初始化顺序很重要,或者执行一些在使用静态成员的类型的简单初始化时不会发生的额外工作,它可能很有用。
例如:
那。我们已经更改了静态字段的创建顺序,并添加了一个额外的步骤来执行简单的就地初始化无法实现的操作。
并非每个对象都可以在一行中初始化。比如我经常使用的一个类,
XmlSerializerNamespaces在调用构造函数后,需要多次调用Add方法。或者,如果您需要通过 Linq 表达式构建委托,则必须单独创建参数,尽管表达式的其余部分通常可以写在一行中。
有时需要更复杂的初始化逻辑。
此外,您需要了解在 C# 中,静态字段初始值设定项不同于静态构造函数。在编译过程中,它们都是简单地添加到它的开头。
如果我们把一个静态类看作一个模板,那么这是一个“自由”的单例,类的所有静态成员都是这个单例的成员。同时,这个静态类在向它传递一个非静态类的实例时,将拥有对这个非静态类的字段的完全访问权限。
静态类也称为 TYPE 类(静态构造函数分别称为类型构造函数),这意味着即使该类被声明为非静态的,实际上也会为其创建一个静态组件。
好吧,那么我们必须从方便和必要出发:
静态类的“实例”: 1. 无继承 2. 无处不在,无需传递对函数代码的任何引用(根据范围)
非静态类的实例: 1. 有继承 2. 可以有多个实例 3. 只能通过传递给函数的引用访问
例如你有一个类
此代码将等同于代码
如果你使用第一个选项,那么编译器会自动生成一个类型构造函数