C#对象初始化器的用法(附带实例)
在 C# 程序中,为了简化对象的初始化,可以在调用构造器之后直接通过对象初始化器设置对象的可访问字段或属性。
例如下面的类:
构造 b1 和 b2 的代码等价于:
可选参数有两个缺点:
上述问题之所以存在,是因为所有的可选参数都需要由调用者处理。换句话说,C# 会将我们的构造器调用转换为:
还有一种难以发现的错误是,如果我们修改了某个可选参数的默认值,则另一个程序集的调用者在重新编译之前,还会继续使用旧的可选值。
例如下面的类:
public class Bunny { public string Name; public bool LikesCarrots; public bool LikesHumans; public Bunny() { } public Bunny(string n) { Name = n; } }就可以用对象初始化器对 Bunny 对象进行实例化:
// 注意:无参数构造函数可以省略空括号 Bunny b1 = new Bunny { Name="Bo", LikesCarrots=true, LikesHumans=false }; Bunny b2 = new Bunny("Bo") { LikesCarrots=true, LikesHumans=false };
构造 b1 和 b2 的代码等价于:
Bunny temp1 = new Bunny(); // temp1 是编译器生成的名称 temp1.Name = "Bo"; temp1.LikesCarrots = true; temp1.LikesHumans = false; Bunny b1 = temp1; Bunny temp2 = new Bunny("Bo"); temp2.LikesCarrots = true; temp2.LikesHumans = false; Bunny b2 = temp2;使用临时变量是为了确保即使在初始化过程中抛出异常,也不会得到一个部分初始化的对象。
使用对象初始化器还是可选参数
除了使用对象初始化器,还可以令 Bunny 的构造器接收可选参数:public Bunny (string name, bool likesCarrots = false, bool likesHumans = false) { Name = name; LikesCarrots = likesCarrots; LikesHumans = likesHumans; }我们可以使用如下的语句构造 Bunny 对象:
Bunny b1 = new Bunny(name: "Bo", likesCarrots: true);在之前 C# 版本中,这样做的优点是我们可以将 Bunny 的字段(或属性,之后会讲解)设置为只读。如果在对象的生命周期内无须改变字段值或属性值,则这样做是一种良好的实践。
可选参数有两个缺点:
- 首先,虽然在构造器中使用可选参数可以创建只读类型,但是它无法简单地实现非破坏性更改(nondestructive mutation)。
- 其次,在公有库中使用可选参数可能造成向后兼容问题。这是因为后续添加可选参数会破坏该程序集对现有消费者二进制兼容性(如果该公有库发布在 NuGet 上,则更应当重视上述问题:当消费者引用了包 A 和包 B,而这两个包分别依赖于 L 的不兼容版本时问题就变得更加棘手了)。
上述问题之所以存在,是因为所有的可选参数都需要由调用者处理。换句话说,C# 会将我们的构造器调用转换为:
Bunny b1 = new Bunny("Bo", true, false);如果另一个程序集实例化 Bunny,则当 Bunny 类再次添加一个可选参数(如 likesCats)时就会出错。除非引用该类的程序集也重新编译,否则,它还将继续调用三个参数的构造器(现在已经不存在了),从而造成运行时错误。
还有一种难以发现的错误是,如果我们修改了某个可选参数的默认值,则另一个程序集的调用者在重新编译之前,还会继续使用旧的可选值。