C#9.0新特性之Init-only属性–创建不带构造函数的不可变属性

在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属性是只读的。

微信截图_20210525114049.png

现在,我们解决了我们的问题,属性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。如你所见,我收到一条错误消息

微信截图_20210525114728.png

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#中使用不可变数据,那么这绝对是一个非常有用的功能。

感谢阅读!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享