65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 9 的 init-only 设置器

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.65/5 (5投票s)

2021 年 8 月 15 日

CPOL

2分钟阅读

viewsIcon

8592

关于 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、序列化框架等。

希望您喜欢这篇文章,在下一篇文章中,我们将了解 记录

© . All rights reserved.