Mar*_*eIV 7 c# readonly c#-9.0 init-only
我一直在阅读 C#9 中的 init-only 属性,但我认为我们已经有了只能在构造函数中设置的只读属性。在那之后,它是不可变的。
例如,在这里的类中,Name和Description都可以在构造函数中赋值,但只能在构造函数中赋值,这正是 init-only 属性的描述方式。
class Thingy {
public Thingy(string name, string description){
Name = name;
Description = description;
}
public string Name { get; }
public string Description { get; }
public override string ToString()
=> $"{Name}: {Description}";
}
Run Code Online (Sandbox Code Playgroud)
using System;
class Program {
public static void Main (string[] args) {
var thingy = new Thingy("Test", "This is a test object");
Console.WriteLine(thingy);
// thingy.Name = “Illegal”; <— Won’t compile this line
}
}
Run Code Online (Sandbox Code Playgroud)
这将输出以下内容:
class Thingy {
public Thingy(string name, string description){
Name = name;
Description = description;
}
public string Name { get; }
public string Description { get; }
public override string ToString()
=> $"{Name}: {Description}";
}
Run Code Online (Sandbox Code Playgroud)
此外,如果我尝试修改Name或Description在构造函数运行后,它将无法编译。
那么我错过了什么?
一个init访问是相同的set实施访问的几乎所有领域,但它是在一定的方式,使得它的编译器不允许使用外的一些具体的上下文标记。
通过相同的我真的不意味着相同。创建的隐藏方法的名称是set_PropertyName,就像set访问器一样,使用反射你甚至无法区分它们,它们看起来是相同的(请参阅我下面关于此的注释)。
不同之处在于编译器使用此标志(更多内容见下文)将仅允许您在少数特定上下文中为 C# 中的属性设置值(下文也有更多内容)。
new SomeType { Property = value }with关键字的构造中,即。var copy = original with { Property = newValue }init另一个属性的访问器中(因此一个init访问器可以写入其他init访问器属性)[AttributeName(InitProperty = value)]在这些之外,基本上相当于正常的属性分配,编译器将阻止您写入带有编译器错误的属性,如下所示:
CS8852 仅初始化属性或索引器“Type.Property”只能在对象初始值设定项中分配,或者在实例构造函数或“init”访问器中的“this”或“base”上分配。
因此,鉴于这种类型:
public class Test
{
public int Value { get; init; }
}
Run Code Online (Sandbox Code Playgroud)
您可以通过以下所有方式使用它:
var test = new Test { Value = 42 };
var copy = test with { Value = 17 };
...
public class Derived : Test
{
public Derived() { Value = 42; }
}
public class ViaOtherInit : Test
{
public int OtherValue
{
get => Value;
init => Value = value + 5;
}
}
Run Code Online (Sandbox Code Playgroud)
但你不能这样做:
var test = new Test();
test.Value = 42; // Gives compiler error
Run Code Online (Sandbox Code Playgroud)
因此,出于所有意图和目的,这种类型是不可变的,但它现在允许您更轻松地构造该类型的实例,而不会陷入这种不可变性问题。
我在上面说过,反射并没有真正看到这一点,并注意我今天才了解实际机制,所以也许有一种方法可以找到一些可以真正区分差异的反射代码。重要的部分是编译器可以看到差异,这就是。
鉴于类型声明为:
public class Test
{
public int Value1 { get; set; }
public int Value2 { get; init; }
}
Run Code Online (Sandbox Code Playgroud)
那么为这两个属性生成的 IL 将如下所示:
.property instance int32 Value1()
{
.get instance int32 UserQuery/Test::get_Value1()
.set instance void UserQuery/Test::set_Value1(int32)
}
.property instance int32 Value2()
{
.get instance int32 UserQuery/Test::get_Value2()
.set instance void modreq(System.Runtime.CompilerServices.IsExternalInit) UserQuery/Test::set_Value2(int32)
}
Run Code Online (Sandbox Code Playgroud)
您可以看到Value2属性设置器(init方法)已被标记/标记(不确定这些词是否正确,我确实说过我今天学到了这个),其modreq(System.Runtime.CompilerServices.IsExternalInit)类型告诉编译器此方法不是您叔叔的设置访问器。
这就是编译器如何知道将这个访问器方法与普通set访问器区别对待的方式。
鉴于@canton7对这个问题的评论,这个modreq构造也意味着如果你尝试在旧的 C# 编译器中使用用新的 C# 9 编译器编译的库,它不会考虑这个方法。这也意味着您将无法在对象初始值设定项中设置属性,但这当然只能在 C# 9 和更新的编译器中使用。
那么设置值的反射呢?好吧,事实证明反射将能够init很好地调用访问器,这很好,因为这意味着反序列化,您可能会认为它是一种对象初始化,仍然会按您的预期工作。
观察以下LINQPad程序:
void Main()
{
var test = new Test();
// test.Value = 42; // Gives compiler error
typeof(Test).GetProperty("Value").SetValue(test, 42);
test.Dump();
}
public class Test
{
public int Value { get; init; }
}
Run Code Online (Sandbox Code Playgroud)
产生这个输出:
这是一个 Json.net 示例:
void Main()
{
var json = "{ \"Value\": 42 }";
var test = JsonConvert.DeserializeObject<Test>(json);
test.Dump();
}
Run Code Online (Sandbox Code Playgroud)
这给出了与上面完全相同的输出。
不同之处在于init属性可以从对象初始值设定项以及构造函数中设置:
public class C
{
public int Foo { get; init; }
}
// Legal
var c = new C()
{
Foo = 3,
};
// Illegal
c.Foo = 4;
Run Code Online (Sandbox Code Playgroud)
如果您声明带有属性的记录init,编译器还允许您使用with表达式设置它们:
public record C
{
public int Foo { get; init; }
}
var c = new C() { Foo = 3 };
var d = c with { Foo = 4 };
Run Code Online (Sandbox Code Playgroud)
使用反射时它们也显示为可写。这是一个经过深思熟虑的设计决策,允许基于反射的序列化器反序列化为具有仅限 init 属性的对象,而无需进行修改。
public class C
{
public int GetterOnly { get; }
public int InitOnly { get; init; }
}
typeof(C).GetProperty("GetterOnly").CanWrite); // False
typeof(C).GetProperty("InitOnly").CanWrite); // True
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1193 次 |
| 最近记录: |