在 C# 中创建新对象时,{ } 的行为是否类似于 ( )?

Does { } act like ( ) when creating a new object in C#?

我刚刚注意到在构造对象时使用 {} 而不是 () 会得到相同的结果。

class Customer
{
    public string name;
    public string ID {get; set;}
}

static void Main()
{  
    Customer c1= new Customer{}; //Is this a constructor? 
    Customer c2= new Customer();

    //what is the concept behind the ability to assign values for properties 
    //and fields inside the {} and is not allowable to do it inside () 
    //without defining a constructor:

    Customer c3= new Customer{name= "John", ID="ABC"};
}

在 C# 中创建新对象时,{} 是否像 () 一样?

没有新版本的 C# 隐式创建用于对象初始化的构造函数

Customer c1= new Customer{};

以上同

Customer c1= new Customer()
{
};

Object and Collection Initializers (C# Programming Guide)

Customer c1= new Customer{} - 这是属性的初始值设定项。你可以写成:

Customer c1 = new Customer{
              name="some text",
              ID="some id"
              };

You can use object initializers to initialize type objects in a declarative manner without explicitly invoking a constructor for the type.

https://msdn.microsoft.com/en-us/library/bb397680.aspx

如果类型具有默认构造函数,您也可以省略调用构造函数。所以

Customer c1 = new Customer { };

完全相同
Customer c1 = new Customer() { };

() - 调用无参数构造函数。

{} - 应该用于分配属性。

使用不带 (){} 是一种快捷方式,只要有无参数构造函数就可以使用。

C#中直接创建新对象的三种方式:

  • 带有参数列表的简单构造函数调用:

    new Foo()       // Empty argument list
    new Foo(10, 20) // Passing arguments
    
  • 带有参数列表的对象初始值设定项

    new Foo() { Name = "x" }       // Empty argument list
    new Foo(10, 20) { Name = "x" } // Two arguments
    
  • 没有参数列表的对象初始值设定项

    new Foo { Name = "x" }
    

最后一种形式完全等同于指定空参数列表。通常它会调用一个无参数的构造函数,但它可以调用一个所有参数都有默认值的构造函数。

现在,在我给出的两个对象初始值设定项示例中,我都设置了 Name 属性 - 您可以设置其他 properties/fields,甚至设置 no 属性和字段。所以这三个都是等价的,实际上没有传递构造函数参数并且没有指定 properties/fields 来设置:

new Foo()
new Foo() {}
new Foo {}

其中,第一个是最常规的。

在您的特定情况下,是的,它会产生相同的结果,但不是出于您可能认为的原因。

为了理解结果,假设您有一个像这样的 class:

class Customer
{
    public string name;
    public string ID {get; set;}

    public Customer() 
    { 
    }

    public Customer(string n, string id)
    {
        name = n;
        ID = id;
    }
}

当你这样创建它时:

Customer c = new Customer("john", "someID");

您调用第二个构造函数并告诉 class 您正在传递这些值,并且执行它认为最好的任务的责任在于构造函数(在这种情况下,它将使用这些值来传递他们到 public 个字段)。

当你这样创建它时:

Customer c = new Customer { name = "john", ID = "someID" };

您自己设置了 public 字段,并且使用了空构造函数。

无论哪种方式,您都应该避免使用 public 字段,因为它不安全。你不应该让任何外部的人像这样直接修改它们。而是仅使用私有字段并使用 public 属性来管理来自外部的访问!

Customer c1 = new Customer {};

这是一个空的对象初始值设定项。根据 spec,对象初始值设定项将调用默认构造函数,除非您指定要使用的构造函数。由于没有进行初始化,所以会像使用默认构造函数一样编译。

要回答这个问题,如果“{} 在 C# 中创建新对象时表现得像 ()”,我们必须更详细地说明。对于您关心的所有内容,生成的对象将包含相同的数据,但生成的 IL 并不相同。

下面的例子

namespace SO28254462
{
    class Program
    {
        class Customer
        {
            private readonly Foo foo = new Foo();

            public string name;

            public Customer()
            {
            }

            public Customer(string id)
            {
                this.ID = id;
            }

            public string ID { get; set; }

            public Foo Foo
            {
                get
                {
                    return this.foo;
                }
            }
        }

        class Foo
        {
            public string Bar { get; set; }
        }

        static void Main(string[] args)
        {
            Customer c1 = new Customer { };

            Customer c2 = new Customer();

            Customer c3 = new Customer { name = "John", ID = "ABC", Foo = { Bar = "whatever" } };

            Customer c4 = new Customer();
            c4.name = "John";
            c4.ID = "ABC";
            c4.Foo.Bar = "whatever";

            Customer c5 = new Customer("ABC") { name = "John", Foo = { Bar = "whatever" } };

            Customer c6 = new Customer("ABC");
            c6.name = "John";
            c6.Foo.Bar = "whatever";
        }
    }
}

将编译为此 IL(仅主要方法,不进行优化调试)

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 201 (0xc9)
    .maxstack 2
    .entrypoint
    .locals init (
        [0] class SO28254462.Program/Customer c1,
        [1] class SO28254462.Program/Customer c2,
        [2] class SO28254462.Program/Customer c3,
        [3] class SO28254462.Program/Customer c4,
        [4] class SO28254462.Program/Customer c5,
        [5] class SO28254462.Program/Customer c6,
        [6] class SO28254462.Program/Customer '<>g__initLocal0',
        [7] class SO28254462.Program/Customer '<>g__initLocal1'
    )

    IL_0000: nop
    IL_0001: newobj instance void SO28254462.Program/Customer::.ctor()
    IL_0006: stloc.0
    IL_0007: newobj instance void SO28254462.Program/Customer::.ctor()
    IL_000c: stloc.1
    IL_000d: newobj instance void SO28254462.Program/Customer::.ctor()
    IL_0012: stloc.s '<>g__initLocal0'
    IL_0014: ldloc.s '<>g__initLocal0'
    IL_0016: ldstr "John"
    IL_001b: stfld string SO28254462.Program/Customer::name
    IL_0020: ldloc.s '<>g__initLocal0'
    IL_0022: ldstr "ABC"
    IL_0027: callvirt instance void SO28254462.Program/Customer::set_ID(string)
    IL_002c: nop
    IL_002d: ldloc.s '<>g__initLocal0'
    IL_002f: callvirt instance class SO28254462.Program/Foo SO28254462.Program/Customer::get_Foo()
    IL_0034: ldstr "whatever"
    IL_0039: callvirt instance void SO28254462.Program/Foo::set_Bar(string)
    IL_003e: nop
    IL_003f: ldloc.s '<>g__initLocal0'
    IL_0041: stloc.2
    IL_0042: newobj instance void SO28254462.Program/Customer::.ctor()
    IL_0047: stloc.3
    IL_0048: ldloc.3
    IL_0049: ldstr "John"
    IL_004e: stfld string SO28254462.Program/Customer::name
    IL_0053: ldloc.3
    IL_0054: ldstr "ABC"
    IL_0059: callvirt instance void SO28254462.Program/Customer::set_ID(string)
    IL_005e: nop
    IL_005f: ldloc.3
    IL_0060: callvirt instance class SO28254462.Program/Foo SO28254462.Program/Customer::get_Foo()
    IL_0065: ldstr "whatever"
    IL_006a: callvirt instance void SO28254462.Program/Foo::set_Bar(string)
    IL_006f: nop
    IL_0070: ldstr "ABC"
    IL_0075: newobj instance void SO28254462.Program/Customer::.ctor(string)
    IL_007a: stloc.s '<>g__initLocal1'
    IL_007c: ldloc.s '<>g__initLocal1'
    IL_007e: ldstr "John"
    IL_0083: stfld string SO28254462.Program/Customer::name
    IL_0088: ldloc.s '<>g__initLocal1'
    IL_008a: callvirt instance class SO28254462.Program/Foo SO28254462.Program/Customer::get_Foo()
    IL_008f: ldstr "whatever"
    IL_0094: callvirt instance void SO28254462.Program/Foo::set_Bar(string)
    IL_0099: nop
    IL_009a: ldloc.s '<>g__initLocal1'
    IL_009c: stloc.s c5
    IL_009e: ldstr "ABC"
    IL_00a3: newobj instance void SO28254462.Program/Customer::.ctor(string)
    IL_00a8: stloc.s c6
    IL_00aa: ldloc.s c6
    IL_00ac: ldstr "John"
    IL_00b1: stfld string SO28254462.Program/Customer::name
    IL_00b6: ldloc.s c6
    IL_00b8: callvirt instance class SO28254462.Program/Foo SO28254462.Program/Customer::get_Foo()
    IL_00bd: ldstr "whatever"
    IL_00c2: callvirt instance void SO28254462.Program/Foo::set_Bar(string)
    IL_00c7: nop
    IL_00c8: ret
} // end of method Program::Main

正如我们所见,前两个 Customer 初始化已转换为相同的 IL(IL_0001 到 IL_000c)。之后,它变得更有趣了:从 IL_000d 到 IL_0041,我们看到创建了一个新对象并将其分配给不可见的临时变量 <>g__initLocal0,只有在完成之后,结果值分配给 c3。这就是 C# 编译器实现对象初始值设定项的方式。当您查看 c4 如何从 IL_0042 初始化为 IL_006a 时,在实例化对象后设置 public 属性 "manually" 的区别很明显 - 没有临时变量。

IL_0070 直到最后都等同于前面的示例,除了它们使用非默认构造函数和对象初始值设定项。如您所料,它只是按照与以前相同的方式进行编译,但使用指定的构造函数。

长话短说:从 C# 开发人员的角度来看,结果基本相同。对象初始值设定项是简单的语法糖和编译器技巧,有助于使代码更易于阅读。但是 FWIW,生成的 IL 与属性的手动初始化不同。尽管如此,编译器发出的不可见临时变量的成本在几乎所有 C# 程序中都应该是微不足道的。