C# 9 的 init-only 设置器






3.65/5 (5投票s)
关于 C# 9 中引入的 "init" 关键字的讨论
在之前的一篇文章中,我们讨论了空对象模式,并讨论了一种hackish的方法来确保默认属性无法更改。
请注意,本文中的代码片段是在 LinqPad 中运行的,因此可以直接使用。要在 Visual Studio 中运行它们,可能需要进行一些小的更改。
在这篇文章中,我们将讨论 C# 9 与 .NET 5 中引入的相对较新的关键字,即 init
关键字。
它是如何工作的?
此关键字将允许我们创建不可变对象(至少在标记了 init
的属性方面),从而确保没有人可以稍后更改其值。
那么我们如何使用这个关键字呢? 实际上,这个关键字应该在声明属性时代替 set
关键字,如下所示
class TestClass1
{
public int TestClass1IntProperty { get; set; }
}
class TestClass2
{
public int TestClass1IntProperty {get; init; }
}
这些属性的使用方式如下所示
void Main()
{
TestClass1 test1 = new TestClass1
{
TestClass1IntProperty = 42
};
test1.TestClass1IntProperty = 43; // this is ok because the property is mutable.
TestClass2 test2 = new TestClass2
{
TestClass2IntProperty = 42
};
// this will show the following compiler error:
// CS8852 Init-only property or indexer '<full name of property>'
// can only be assigned to an object initializer,
// or on 'this' or 'base' in an instance constructor or an 'init' accessor.
test2.TestClass2IntProperty = 43;
}
由于此关键字代替了 set
关键字用于属性,这意味着我们也可以声明只有部分属性标记为 init
的类,以实现不可变性,如下所示
class TestClass3
{
public int TestClass3IntProperty { get; set; }
public int TestClass3IntProperty2 { get; init; }
}
此外,由于我们讨论的是属性,我们也讨论了带有后备字段的生成方法。因此,我们可以在初始化实例时运行自定义代码来设置属性的值,如下所示
class TestClass4
{
private int _testClass4IntProperty;
public int TestClass4IntProperty
{
get => _testClass4IntProperty;
init => _testClass4IntProperty = value;
}
private int _testClass4IntProperty2;
public int TestClass4IntProperty2
{
get { return _testClass4IntProperty2; }
init { _testClass4IntProperty2 = value; }
}
}
到目前为止一切都很好,在这方面没有太多需要探索的,尽管该功能对于设计更安全的代码非常强大,但让我们看看其他一些场景。
需要牢记的事项
引用属性仅保留引用
假设我们有以下类
class TestClass5
{
public int TestClass5IntProperty { get; set; }
}
class TestClass6
{
public TestClass5 TestClass6RefProperty { get; init; }
}
就像将字段声明为 readonly
或具有仅 getter 属性一样,这仅指该字段/属性中包含的实例,因此我们仍然可以更改由不可变字段/属性引用的实例的非不可变属性。
void Main()
{
TestClass6 test = new TestClass6
{
TestClass6RefProperty = new TestClass5
{
TestClass5IntProperty = 42
}
};
// this is valid code because the reference to the instance
// of TestClass6RefProperty has not changed.
test.TestClass6RefProperty.TestClass5IntProperty = 445;
// this will throw an error because we're trying
// to change the reference of the init property
test.TestClass6RefProperty = new TestClass5 { TestClass5IntProperty = 13 };
}
反射仍然可以绕过限制
让我们看一下以下代码并解释为什么它有效,为了简洁起见,我们将使用之前定义的 TestClass2
。
void Main()
{
TestClass2 test = new TestClass2
{
TestClass2IntProperty = 42
};
Console.WriteLine(test.TestClass2IntProperty); // will output 42
PropertyInfo setPropertyInfo = test.GetType().GetProperty
(nameof(TestClass2.TestClass2IntProperty));
setPropertyInfo.SetValue(test, 512);
Console.WriteLine(test.TestClass2IntProperty); // will output 512
}
如果在上面的示例中运行代码,我们会看到我们成功更改了值并且没有编译错误,因此仅仅因为开发人员不允许直接更改不可变属性的值,并不意味着他们不能这样做。
我相信这是可行的原因在于许多框架依赖于反射来完成其工作。像 Entity Framework 和其他 ORM、序列化框架等。
希望您喜欢这篇文章,在下一篇文章中,我们将了解 记录。