在2007年11月,Microsoft在.NET Framework 3.5发行版中引入了C#3.0。C#3.0引入了许多新概念,例如语言集成查询(LINQ)语法。它还介绍了对象初始化器的强大概念。它们是C#9.0引入的新的仅初始化属性的基础,因此让我们从对象初始化程序开始。
了解对象初始化器
比方说,你有一个Friend带有两个属性的类FirstName和LastName:
public class Friend
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
复制代码
在此之前,你可以通过调用Friend的默认构造函数并在此之后初始化属性来创建如下所示的对象:
var friend = new Friend();
friend.FirstName = "易烊";
friend.LastName = "千玺";
复制代码
除了上面的三个语句,你还可以在对象初始化的同时赋值,如下面的代码片段所示。对象初始化还是会调用默认的构造函数,然后通过调用它们的setter方法初始化两个属性FirstName和LastName:
var friend = new Friend
{
FirstName = "易烊",
LastName = "千玺"
};
复制代码
创建不可变属性
对象初始化程序的问题在于它们不允许你创建不可变的属性。但是在现在的情况下,你可以在创建对象后随时更改属性值。下面的代码片段显示了这一点:
var friend = new Friend
{
FirstName = "易烊",
LastName = "千玺"
};
friend.FirstName = "Jackson";
复制代码
要使属性不可变,你必须创建一个构造函数,如下面的代码片段所示,该构造函数将FirstName和LastName作为参数。请注意,我已经删除了属性的setter,这意味着在初始化对象之后不能更改属性。这与只读字段的逻辑相同,它们也可以直接初始化或在构造函数中初始化。
public class Friend
{
public Friend(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; }
public string LastName { get; }
}
复制代码
上面的代码定义的类,创建对象后便无法更改FirstName或LastName属性。在下面的代码中,你会看到一条错误消息,指出该FirstName属性是只读的。
现在,我们解决了我们的问题,属性FirstName和LastName是不可变的。但是要实现这一点,我们必须定义一个构造函数。
使用C#9.0,你可以创建不带构造函数的不可变属性:使用Init-Only属性。
C#9.0中Init-Only属性的概念
在下面的代码中,你将看到一个与之前代码中定义的Friend类几乎完全相同的类
public class Friend
{
public Friend(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; init; }
public string LastName { get; init; }
}
复制代码
区别在于init关键字。init关键字用于定义一个特殊的set访问。这些属性现在称为Init-Only属性。这意味着,你只能在构造函数中初始化这些属性,如上面的代码一样,或像下面的代码一样……
public string FirstName { get; init; } = "易烊";
复制代码
Init-Only属性和对象初始化程序
在下面的代码中,你将看到一个仅具有init属性而没有构造函数的Friend类。实际上,Friend类看起来与本文开头显示的类完全相同。唯一的区别是,我们在自动属性中使用的是init关键字而不是set关键字,这使它们成为Init-Only属性。
public class Friend
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
复制代码
Friend类没有构造函数,所以我们只能使用对象初始化器:
var friend = new Friend
{
FirstName = "易烊",
LastName = "千玺"
};
复制代码
居然有效!现在也实现了在创建对象之后不能更改属性的效果。这就是Init-Only属性的作用。在下面的屏幕截图中,我尝试在初始化Friend对象之后设置属性FirstName。如你所见,我收到一条错误消息
Init-Only属性的值是可选的
值得一提的是,仅初始化属性的值不是必需的,它们是可选的。例如,你可以按照下面的代码设置Friend对象的FirstName属性,这是完全可以的。但是,由于我们使用Init-Only属性,因此你无法在此语句后设置属性LastName,因为现在属性是不可变的:
var friend = new Friend
{
FirstName = "易烊",
};
复制代码
设置只读字段
由于init在对象初始化期间调用了Init-only属性的访问器,因此可以在访问器中设置readonly字段,其初始化方式与在构造函数中设置字段的方式完全相同。也可以检查分配的属性值是否有效。例如,分配null或空格对于属性FirstName和LastName并没有多大意义。因此,你可以在属性访问器中设置字段。这些属性仍然是不可变的,但是如果在对象初始值设定项中分配null或空格,因为没有定义构造函数,你会得到一个ArgumentException。
public class Friend
{
private readonly string _firstName;
private readonly string _lastName;
public string FirstName
{
get => _firstName;
init => _firstName = string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("不能为null或空格",
nameof(FirstName))
: value;
}
public string LastName
{
get => _lastName;
init => _lastName = string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("不能为null或空格",
nameof(LastName))
: value;
}
}
复制代码
概括
仅初始化属性是一个强大的功能。它们使你可以创建不可变属性,而无需定义构造函数。在通过构造函数设置Init-only属性的同时,你也可以使用对象初始化程序设置Init-only属性,之后便无法修改它们,因为它们是不可变的。如果你打算在C#中使用不可变数据,那么这绝对是一个非常有用的功能。
感谢阅读!