【问题标题】:C# - What's the difference between these two ways of instancing a class property?C# - 这两种实例化类属性的方式有什么区别?
【发布时间】:2011-01-28 19:48:38
【问题描述】:

这里有基本的 C# 问题。

在声明类属性/字段时或在相关对象的构造函数中创建类属性/字段的实例有什么区别。例如:

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

对

public class MyClass
{
    public MyObject;

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

【问题讨论】:

    标签: c#


    【解决方案1】:

    具有初始化器的字段在调用基础构造函数之前在被初始化,而如果初始化器在主体中,则仅在调用基础构造函数之后被执行.

    如果基础构造函数调用虚方法,这可能是相关的 - 但我个人会尽量避免这种情况。

    示例代码:

    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
        }
    }
    

    【讨论】:

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

    我编译了这些 C# 代码:

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

    我得到了 IL 程序集:

    我的班级1:

    .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
    

    我的班级2:

    .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 初始化器
    • 类初始化器(构造函数赋值)

    【讨论】:

      【解决方案3】:

      您还可以使用静态构造函数,该构造函数在您可以初始化静态变量的任何其他构造函数之前被调用

      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();
          }
      }
      

      【讨论】:

        【解决方案4】:

        重要的是要注意(在 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 答案的评论。
        • @Artur:如果不先阅读它,您会在哪里下车?您的小测试用 C# 编译了代码。 Damien 在这里的回答以及他链接的my question 明确指出行为是VB.NET 中的不同。
        • @Artur - 哪一点不正确?您的答案(Class2)中的赋值MyObject = new MyObject() 不是初始化程序,它只是一个赋值语句。初始化器在C#中有特定的含义
        • @Cody Gray: "...在 C# 中,对字段的初始化赋值将在调用任何基类构造函数之前发生" - 这是一个谎言
        • @Damien_The_Unbeliever 在 MyClass2 中,在基类构造函数调用之后调用字段的初始化程序 ASSIGMENT
        猜你喜欢
        • 2013-06-15
        • 2019-04-04
        • 2013-05-17
        • 2020-12-08
        • 1970-01-01
        • 2017-01-26
        • 2021-12-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多