C# - 这两种实例化类属性的方式有什么区别?

12

基础的C#问题。

如何区分在声明时和在对象构造函数中创建类属性/字段实例的区别。例如:

public class MyClass
{
    public MyObject = new MyObject();
}

对比

public class MyClass
{
    public MyObject;

    public MyCLass()
    {
        MyObject = new MyObject();
    }
}
4个回答

23

如果一个字段有初始化器,那么它将在调用基类构造函数之前进行初始化,而如果初始化器在主体中,则该初始化程序仅在调用基类构造函数后执行。

如果基类构造函数调用虚方法,则这可能是相关的 - 但个人建议避免这种情况。

示例代码:

public class Base
{
    public Base()
    {
        Dump();
    }

    public virtual void Dump() {}
}

public class Child : Base
{
    private string x = "Initialized at declaration";
    private string y;

    public Child()
    {
        y = "Initialized in constructor";
    }

    public override void Dump()
    {
        Console.WriteLine(x); // Prints "Initialized at declaration"
        Console.WriteLine(y); // Prints "" as y is still null
    }
}

此外,我的理解是,在声明时初始化字段允许编译器/JITer执行一些优化,否则在构造函数中进行初始化则无法实现。我相信我在“.NET Framework设计指南”中读到过这一点,作为他们倾向于使用字段初始化作为“最佳实践”的理由,但我不再确定了。 - Cody Gray
@Cody:说实话,我不确定。我个人写代码时不会考虑这一点 - 我只会坚持表达意图最清晰的方式。 - Jon Skeet
@Jon Skeet 的错误:在代码中子类未继承自基类。 - Adeel
@Jon Skeet,关于这个问题,如果在Reflector中看到,编译器将把示例2(在问题中)转换为示例1。 - Adeel
2
@Adeel:在我看来,那基本上是反编译器中的一个错误。查看 IL(你仍然可以在反编译器中进行)你会发现在赋值和基础构造函数调用的顺序方面存在差异。 - Jon Skeet

3

我编译这些C#代码:

public class MyClass1
{
    public MyObject MyObject = new MyObject();
}
public class MyClass2
{
    public MyObject MyObject;

    public MyClass2()
    {
        MyObject = new MyObject();
    }
}

I got IL assembly:

MyClass1:

.class public auto ansi beforefieldinit test.MyClass1
       extends [mscorlib]System.Object
{
  .field public class test.MyObject MyObject
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // code size:       19 (0x13)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  newobj     instance void test.MyObject::.ctor()
    IL_0006:  stfld      class test.MyObject test.MyClass1::MyObject
    IL_000b:  ldarg.0
    IL_000c:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0011:  nop
    IL_0012:  ret
  } // end of method MyClass1::.ctor

} // end of class test.MyClass1

MyClass2:

.class public auto ansi beforefieldinit test.MyClass2
       extends [mscorlib]System.Object
{
  .field public class test.MyObject MyObject
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // code size:       21 (0x15)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  nop
    IL_0008:  ldarg.0
    IL_0009:  newobj     instance void test.MyObject::.ctor()
    IL_000e:  stfld      class test.MyObject test.MyClass2::MyObject
    IL_0013:  nop
    IL_0014:  ret
  } // end of method MyClass2::.ctor
} // end of class test.MyClass2

很明显,区别只在于调用基类构造函数(System.Object::.ctor())、MyObject初始化器(test.MyObject::.ctor())和类初始化器(stfld class test.MyObject test.MyClass2::MyObject)的顺序。
对于第一种情况,MyClass1的初始化顺序如下:
- MyObject初始化器 - 类初始化器(构造函数赋值) - 基类初始化器(基类构造函数)
但是,MyClass2的初始化顺序如下:
- 基类初始化器(基类构造函数) - MyObject初始化器 - 类初始化器(构造函数赋值)

2
你可以使用静态构造函数,在任何其他构造函数之前调用,你可以在其中初始化静态变量。
public class Bus
{
   private static object m_object= null;

    // Static constructor:
    static Bus()
    {
        m_object = new object();

        System.Console.WriteLine("The static constructor invoked.");
    }

    public static void Drive()
    {
        System.Console.WriteLine("The Drive method invoked.");
    }
}

class TestBus
{
    static void Main()
    {
        Bus.Drive();
    }
}

1

需要注意的是(在C#中),字段的初始化赋值将发生在任何基类构造函数的调用之前(如 this question 中所证明的,关于 VB 是否可以被强制执行相同的操作)。

这意味着您不能使用初始化器语法来引用基类的字段(即,您无法直接将此VB转换为C#):

Public Class Base
    Protected I As Int32 = 4
End Class

Public Class Class2
    Inherits Base

    Public J As Int32 = I * 10
End Class

哈哈,我正想把这个作为对Jon Skeet答案的评论发出来。 - Cody Gray
@Artur:你怎么能在没有阅读它的情况下就对它进行负面评价呢?你的小测试编译了C#代码。Damien在这里的回答以及我的问题中他链接到的明确指出,这种行为在VB.NET中是不同的。 - Cody Gray
@Artur - 哪一点不正确?你回答中(Class2)的赋值MyObject = new MyObject()不是初始化器,而只是一个赋值语句。在C#中,初始化器具有特定的含义。 - Damien_The_Unbeliever
@Cody Gray:“……在C#中,字段的初始化赋值将在调用任何基类构造函数之前发生”——这是个谎言。 - Alan Turing
在MyClass2中,初始化器将在调用基类构造函数之后对字段进行赋值。 - Alan Turing
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接