对象初始化器和构造函数有什么区别?

204

两者之间有什么不同,何时应该使用“对象初始化程序”而不是“构造函数”,反之亦然?我正在使用C#,如果这很重要。此外,“对象初始化程序”方法是否专属于C#或.NET?

8个回答

265

对象初始化器是在C# 3中添加的一种功能,它旨在简化在使用对象时构造对象的过程。

构造函数会在给定0个或多个参数的情况下运行,并用于在调用方法获取创建的对象句柄之前创建和初始化一个对象。例如:

MyObject myObjectInstance = new MyObject(param1, param2);
在这种情况下,MyObject的构造函数将使用参数param1param2运行。这两个参数都用于在内存中创建新的MyObject。使用这些参数设置的创建对象将被返回,并设置为myObjectInstance
通常,构造函数应要求需要的参数以完全设置对象,以便无法创建处于无效状态的对象。但是,通常可以设置“额外”属性,但这些属性不是必需的。这可以通过重载构造函数来处理,但会导致有许多构造函数,并非在大多数情况下都有用。
这导致了对象初始化器-对象初始化程序允许您在对象构造完成后,在使用任何其他东西之前设置对象的属性或字段。例如:
MyObject myObjectInstance = new MyObject(param1, param2)
{
    MyProperty = someUsefulValue
};
这会表现得和你这样做差不多:
MyObject myObjectInstance = new MyObject(param1, param2);
myObjectInstance.MyProperty = someUsefulValue;

然而,在多线程环境下,对象初始化的原子性可能是有益的,因为它可以防止对象处于未完全初始化的状态(有关详细信息,请参见此答案)- 它要么为 null,要么像你预期的那样被初始化。

此外,对象初始化器更易于阅读(特别是当您设置多个值时),因此它们给您带来了与构造函数的多个重载相同的好处,而无需为该类的API复杂化而需要许多重载。


50
我同意,但我想补充一点,调试对象初始化器可能会很痛苦,特别是如果你有嵌套的结构,因为对象初始化器被认为是一行代码(格式良好),就像我的这句话。 - CrnaStena
@Reed-Copsey 的解释非常好,使用对象初始化器是否会带来性能提升,还是只是为了可读性? - Mahender
2
@Mahender 实际上,使用对象初始化程序通常会有(非常非常小的)性能损失。构造函数可以编写得更有效率。 - Reed Copsey
6
你的回答很好,但是你的例子是错误的。请根据@nawfal的回答更新你的代码。由于你的回答有很多赞,所以没有人会看其他回答,这会让很多人对对象初始化器的工作原理有错误的理解。 - George Vovos
2
你需要权衡代码可读性和正确性。构造函数中的参数强制你设置所有变量。如果你忘记设置一个变量,编译器会报错。如果你在使用初始化列表时忘记了一个变量,你直到运行时才会注意到它(甚至可能不是立即)。此外:该字段不能是只读的,这使得意外更改成为可能,而编译器不会报错。 - Harald Coppoolse
显示剩余3条评论

53

构造函数是一种在类型上定义的方法,它接受指定数量的参数,并用于创建和初始化对象。

对象初始化器是在构造函数之后运行于对象上的代码,可用于简洁地设置对象上任意数量的字段为指定值。这些字段的设置发生在构造函数被调用之后

如果构造函数已足够设置了对象的初始状态,则可以不使用对象初始化器来使用构造函数。但是,对象初始化器必须与构造函数一起使用。该语法要求显式或隐式使用(VB.Net和C#)构造函数来创建初始对象。如果构造函数没有足够初始化对象以供使用,仅需进行一些简单的字段和/或属性设置即可使用对象初始化器。


可能最好加上当调用对象初始化程序时调用的是默认构造函数,以增加清晰度。 - Abraham Philip
我刚刚解决了一个讨厌的空引用问题,因为有人认为它不是空引用。 - Daniel Sharp
@AbrahamPhilip 这并不一定是正确的。你也可以同时使用带参数的构造函数和对象初始化器。 - nepp95
哎呀,你说得完全正确,谢谢 @nepp95! - Abraham Philip

37
当你做的时候
Person p = new Person { Name = "a", Age = 23 };

这就是对象初始化程序的基本功能:

Person tmp = new Person(); //creates temp object calling default constructor
tmp.Name = "a";
tmp.Age = 23;
p = tmp;

现在这样做可以方便地实现类似于this的行为。了解对象初始化器的工作原理非常重要。

可以直接写成 Person p = new Person {Name = "a"} 吗? - h-rai
@nick-s 是的,非常正确。这就是对象初始化器的作用。您可以初始化您想要的成员。 - nawfal
4
我不知道构造函数的括号可以省略,谢谢。 - anar khalilov
1
@AnarKhalilov 不仅如此,我发现没有括号更加优雅。虽然这可能会使表达得更差一些 [括号告诉我们构造函数已被调用]。 - nawfal

20
如果您的对象必须设置某些属性才能正常工作,则一种方法是仅公开一个单个构造函数,该函数需要这些强制属性作为参数。
在这种情况下,您无法创建没有指定这些必需属性的对象。这样的事情不能由对象初始化程序强制执行。
对象初始化程序实际上只是一种“语法便利”,可缩短初始分配时间。不错,但实际上并没有非常重要的功能相关性。
Marc

2
确保在对象构造时设置必要属性的另一种方法是在类上提供一个静态("工厂")方法,返回一个已构造的对象。该工厂方法通过私有构造函数构造对象。(定义多个私有构造函数可能很有用。) - DavidRR
1
此外,有时创建一个静态工厂类来负责构造一个或多个其他类的实例是很有用的。例如,请参见 File.Create Method (String)(以及它的重载),该方法创建一个 FileStream 对象。 - DavidRR

4

构造函数是一种 (可能) 接受参数并返回一个类的新实例的方法。它可以包含初始化逻辑。下面是一个构造函数的示例。


public class Foo
{
    private SomeClass s;
    public Foo(string s)
    {
       s = new SomeClass(s);
    }
}

现在考虑以下例子:

public class Foo
{
    public SomeClass s { get; set; }
    public Foo() {}
}

如果你可以访问SomeClass,你可以使用对象初始化程序来实现与第一个示例相同的结果,代码如下:


new Foo() { s = new SomeClass(someString) }

正如您所见,对象初始化程序允许您在执行构造时同时为公共字段和公共(可设置)属性指定值,这在构造函数不提供初始化某些字段的重载时尤其有用。 请注意,对象初始化程序只是语法糖,在编译后与一系列分配并没有真正的区别。

1

现在,多年之后,我正在重新考虑使用构造函数而不是对象初始化。 我一直喜欢对象初始化,因为它们快捷简单。选择要设置的字段,然后设置它们就行了。

但是,nullable context应运而生,其中必须指定哪些属性可为空,哪些不可为空。如果您忽略使用构造函数,而是使用对象初始化,编译器将不能确保您的对象实际上是完整的(不存在应该是非空的空属性),但是一个正确编写和使用的构造函数可以解决所有这些问题。

但是,更好的解决方案是对预期在创建时填充的字段使用"required"关键字,无论是通过构造函数还是对象初始化。 这是C# 11的一个新关键字,带有.net 7。


1
对象初始化器可以用于初始化一些小的集合,这些集合可以在初始程序创建阶段用于测试目的。以下是代码示例:
    class Program
    {
        static void Main(string[] args)
        {
            List<OrderLine> ordersLines = new List<OrderLine>()
            {
                new OrderLine {Platform = "AmazonUK", OrderId = "200-2255555-3000012", ItemTitle = "Test product 1"},
                new OrderLine {Platform = "AmazonUK", OrderId = "200-2255555-3000013", ItemTitle = "Test product 2"},
                new OrderLine {Platform  = "AmazonUK", OrderId = "200-2255555-3000013", ItemTitle = "Test product 3"}
            };
        }
    }
    class OrderLine
    {
        public string Platform { get; set; }
        public string OrderId { get; set; }
        public string ItemTitle { get; set; }
    }

这里有一个要注意的地方。在上面的代码示例中没有包含任何构造函数,但它可以正常工作。但是,如果像下面这个例子一样,在OrderLine类中包含了带参数的构造函数:
public OrderLine(string platform, string orderId, string itemTitle)
{
   Platform = platform;
   OrderId = orderId;
   ItemTitle = itemTitle;
}

编译器会显示错误 - 没有给定与所需形式参数相对应的参数.... 可以通过在OrderLine类中包含显式无参数默认构造函数来解决此问题:
public OrderLine() {}

0

对象初始化器在LINQ查询表达式中特别有用。查询表达式经常使用匿名类型,这些类型只能通过使用对象初始化器来初始化,如下面的代码示例所示:

var orderLineReceiver = new { ReceiverName = "Name Surname", ReceiverAddress = "Some address" };

更多关于它的内容 - 对象和集合初始化器


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