C# 6 自动初始化 属性 和支持字段的使用
C# 6 Auto Initialization Property and the use of backing fields
在 C# 6 之前,属性的初始化不使用支持字段来初始化默认值。
在 C#6 中,它使用支持字段以 new Auto initialization properties 进行初始化。
我很好奇为什么在 C#6 之前 IL 使用 属性 定义来初始化。这有什么具体原因吗?还是在 C#6 之前没有正确实现?
C# 6.0 之前
public class PropertyInitialization
{
public string First { get; set; }
public string Last { get; set; }
public PropertyInitialization()
{
this.First = "Adam";
this.Last = "Smith";
}
}
编译器生成的代码(IL 表示)
public class PropertyInitialisation
{
[CompilerGenerated]
private string \u003CFirst\u003Ek__BackingField;
[CompilerGenerated]
private string \u003CLast\u003Ek__BackingField;
public string First
{
get
{
return this.\u003CFirst\u003Ek__BackingField;
}
set
{
this.\u003CFirst\u003Ek__BackingField = value;
}
}
public string Last
{
get
{
return this.\u003CLast\u003Ek__BackingField;
}
set
{
this.\u003CLast\u003Ek__BackingField = value;
}
}
public PropertyInitialisation()
{
base.\u002Ector();
this.First = "Adam";
this.Last = "Smith";
}
}
C#6
public class AutoPropertyInitialization
{
public string First { get; set; } = "Adam";
public string Last { get; set; } = "Smith";
}
编译器生成的代码(IL 表示)
public class AutoPropertyInitialization
{
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string \u003CFirst\u003Ek__BackingField;
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string \u003CLast\u003Ek__BackingField;
public string First
{
get
{
return this.\u003CFirst\u003Ek__BackingField;
}
set
{
this.\u003CFirst\u003Ek__BackingField = value;
}
}
public string Last
{
get
{
return this.\u003CLast\u003Ek__BackingField;
}
set
{
this.\u003CLast\u003Ek__BackingField = value;
}
}
public AutoPropertyInitialization()
{
this.\u003CFirst\u003Ek__BackingField = "Adam";
this.\u003CLast\u003Ek__BackingField = "Smith";
base.\u002Ector();
}
}
我假设您的 C# 5.0 代码如下所示:
class C
{
public C()
{
First = "Adam";
}
public string First { get; private set; }
}
然后在 C# 6.0 中,您所做的唯一更改是使 First
成为 get
-only auto属性:
class C
{
public C()
{
First = "Adam";
}
public string First { get; }
}
在 C# 5.0 的情况下,First
是一个带有 setter 的 属性 并且您在构造函数中使用它,所以生成的 IL 反映了这一点。
在C# 6.0 版本中,First
没有setter,因此构造函数必须直接访问支持字段。
这两种情况对我来说都很有意义。
唯一有区别的是 属性 setter 比简单设置值有更多的效果。对于自动实现的属性,唯一可能发生的情况是它们被 virtual
和覆盖。在那种情况下,在基础 class 构造函数具有 运行 之前调用派生的 class 方法是一个非常糟糕的主意。 C# 经历了很多麻烦,以确保您不会意外地以对尚未完全初始化的对象的引用结束。所以它必须直接设置字段来防止这种情况。
请记住,设置为属性默认值的值不会在构造函数中设置(您的代码表明:赋值,然后是构造函数)。
现在,C# 规范规定自动初始化值在构造函数之前设置。这是有道理的:当在构造函数中再次设置这些值时,它们将被覆盖。
现在 - 在调用构造函数之前 - 没有初始化 getter 和 setter 方法。它们应该如何使用?
这就是为什么(当时未初始化的支持字段)被直接初始化的原因。
正如hvd所说,虚拟调用也会有问题,但它们甚至没有初始化是主要原因。
如果您在构造函数中赋值,它的行为仍然与以前相同:
Example with property that is autoinitialized and changed in the ctor
为什么不优化这个?
有关此主题,请参阅 :
但它不应该优化它吗?
It probably could, but only if that class doesn't inherit from another
class that uses that value in its constructor, it knows that it's an
auto-property and the setter doesn't do anything else.
That would be a lot of (dangerous) assumptions. The compiler needs to
check a lot of things before making an optimization like that.
旁注:
我假设您使用某种工具来查看编译器生成的 c# 代码 - 它并不完全准确。为构造函数生成的 IL 代码没有准确的表达式 - ctor 不是 IL 中的方法,它有些不同。为了便于理解,我们可以假设它是相同的。
http://tryroslyn.azurewebsites.net/ 例如有这样的评论:
// This is not valid C#, but it represents the IL correctly.
获得所示代码的一种方法是您拥有如下所示的 C# 5 代码:
public class Test : Base
{
public Test()
{
A = "test";
}
public string A { get; set; }
}
这将生成如下 (IL) 代码:
public Test..ctor()
{
Base..ctor();
A = "test";
}
您的 C# 6 代码将如下所示:
public class Test : Base
{
public Test()
{
}
public string A { get; set; } = "test";
}
生成如下 (IL) 代码:
public Test..ctor()
{
<A>k__BackingField = "test";
Base..ctor();
}
请注意,如果您在构造函数中专门初始化 属性,并且有一个 getter/setter 属性,在 C# 6 中它仍然看起来像我的第一段代码上面的答案,而如果你有一个 getter-only 字段,它将看起来像这样:
public Test..ctor()
{
Base..ctor();
<A>k__BackingField = "test";
}
很明显,您的 C# 5 代码看起来像上面的第一段代码,而您的 C# 6 代码看起来像第二段代码。
所以回答你的问题:为什么 C# 5 和 C# 6 在编译自动 属性 初始化方面表现不同?原因是你不能在 C# 5 或更早版本中进行自动 属性 初始化,并且不同的代码编译不同。
I'm curious why prior to C#6 IL uses the property definition to initialize. Is there a specific reason for this?
因为通过自动属性 初始化设置值和在构造函数中设置值是两件不同的事情。他们有不同的行为。
回想一下,属性是环绕字段的访问器方法。所以这一行:
this.First = "Adam";
相当于:
this.set_First("Adam");
你甚至可以在 Visual Studio 中看到这个!尝试在您的 class 中编写一个带有签名 public string set_First(string value)
的方法,然后观察编译器抱怨您踩到它的脚趾。
并且就像方法一样,这些可以在子 classes 中被覆盖。查看此代码:
public class PropertyInitialization
{
public virtual string First { get; set; }
public PropertyInitialization()
{
this.First = "Adam";
}
}
public class ZopertyInitalization : PropertyInitialization
{
public override string First
{
get { return base.First; }
set
{
Console.WriteLine($"Child property hit with the value: '{0}'");
base.First = value;
}
}
}
在此示例中,行 this.First = "Adam"
将调用子 class 中的 setter。因为你正在调用一个方法,还记得吗?如果编译器将此方法调用解释为对支持字段的直接调用,则它最终不会调用子 setter。 编译代码的行为会改变程序的行为。不好!
自动属性不同。让我们通过使用 auto-属性 初始值设定项来更改第一个示例:
public class PropertyInitialization
{
public virtual string First { get; set; } = "Adam";
}
public class ZopertyInitalization : PropertyInitialization
{
public override string First
{
get { return base.First; }
set
{
Console.WriteLine($"Child property hit with the value: '{0}'");
base.First = value;
}
}
}
使用此代码,不会调用子 class 中的 setter 方法。这是故意的。 auto-属性 初始化程序旨在直接设置支持字段。它们看起来和行为都像字段初始值设定项,这就是为什么我们甚至可以在没有 setters 的属性上使用它们,如下所示:
public string First { get; } = "Adam";
这里没有setter方法!我们必须直接访问支持字段才能执行此操作。自动属性允许程序员创建不可变的值,同时仍然能够从良好的语法中受益。
在 C# 6 之前,属性的初始化不使用支持字段来初始化默认值。 在 C#6 中,它使用支持字段以 new Auto initialization properties 进行初始化。
我很好奇为什么在 C#6 之前 IL 使用 属性 定义来初始化。这有什么具体原因吗?还是在 C#6 之前没有正确实现?
C# 6.0 之前
public class PropertyInitialization
{
public string First { get; set; }
public string Last { get; set; }
public PropertyInitialization()
{
this.First = "Adam";
this.Last = "Smith";
}
}
编译器生成的代码(IL 表示)
public class PropertyInitialisation
{
[CompilerGenerated]
private string \u003CFirst\u003Ek__BackingField;
[CompilerGenerated]
private string \u003CLast\u003Ek__BackingField;
public string First
{
get
{
return this.\u003CFirst\u003Ek__BackingField;
}
set
{
this.\u003CFirst\u003Ek__BackingField = value;
}
}
public string Last
{
get
{
return this.\u003CLast\u003Ek__BackingField;
}
set
{
this.\u003CLast\u003Ek__BackingField = value;
}
}
public PropertyInitialisation()
{
base.\u002Ector();
this.First = "Adam";
this.Last = "Smith";
}
}
C#6
public class AutoPropertyInitialization
{
public string First { get; set; } = "Adam";
public string Last { get; set; } = "Smith";
}
编译器生成的代码(IL 表示)
public class AutoPropertyInitialization
{
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string \u003CFirst\u003Ek__BackingField;
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string \u003CLast\u003Ek__BackingField;
public string First
{
get
{
return this.\u003CFirst\u003Ek__BackingField;
}
set
{
this.\u003CFirst\u003Ek__BackingField = value;
}
}
public string Last
{
get
{
return this.\u003CLast\u003Ek__BackingField;
}
set
{
this.\u003CLast\u003Ek__BackingField = value;
}
}
public AutoPropertyInitialization()
{
this.\u003CFirst\u003Ek__BackingField = "Adam";
this.\u003CLast\u003Ek__BackingField = "Smith";
base.\u002Ector();
}
}
我假设您的 C# 5.0 代码如下所示:
class C
{
public C()
{
First = "Adam";
}
public string First { get; private set; }
}
然后在 C# 6.0 中,您所做的唯一更改是使 First
成为 get
-only auto属性:
class C
{
public C()
{
First = "Adam";
}
public string First { get; }
}
在 C# 5.0 的情况下,First
是一个带有 setter 的 属性 并且您在构造函数中使用它,所以生成的 IL 反映了这一点。
在C# 6.0 版本中,First
没有setter,因此构造函数必须直接访问支持字段。
这两种情况对我来说都很有意义。
唯一有区别的是 属性 setter 比简单设置值有更多的效果。对于自动实现的属性,唯一可能发生的情况是它们被 virtual
和覆盖。在那种情况下,在基础 class 构造函数具有 运行 之前调用派生的 class 方法是一个非常糟糕的主意。 C# 经历了很多麻烦,以确保您不会意外地以对尚未完全初始化的对象的引用结束。所以它必须直接设置字段来防止这种情况。
请记住,设置为属性默认值的值不会在构造函数中设置(您的代码表明:赋值,然后是构造函数)。
现在,C# 规范规定自动初始化值在构造函数之前设置。这是有道理的:当在构造函数中再次设置这些值时,它们将被覆盖。
现在 - 在调用构造函数之前 - 没有初始化 getter 和 setter 方法。它们应该如何使用?
这就是为什么(当时未初始化的支持字段)被直接初始化的原因。
正如hvd所说,虚拟调用也会有问题,但它们甚至没有初始化是主要原因。
如果您在构造函数中赋值,它的行为仍然与以前相同:
Example with property that is autoinitialized and changed in the ctor
为什么不优化这个?
有关此主题,请参阅
但它不应该优化它吗?
It probably could, but only if that class doesn't inherit from another class that uses that value in its constructor, it knows that it's an auto-property and the setter doesn't do anything else.
That would be a lot of (dangerous) assumptions. The compiler needs to check a lot of things before making an optimization like that.
旁注:
我假设您使用某种工具来查看编译器生成的 c# 代码 - 它并不完全准确。为构造函数生成的 IL 代码没有准确的表达式 - ctor 不是 IL 中的方法,它有些不同。为了便于理解,我们可以假设它是相同的。
http://tryroslyn.azurewebsites.net/ 例如有这样的评论:
// This is not valid C#, but it represents the IL correctly.
获得所示代码的一种方法是您拥有如下所示的 C# 5 代码:
public class Test : Base
{
public Test()
{
A = "test";
}
public string A { get; set; }
}
这将生成如下 (IL) 代码:
public Test..ctor()
{
Base..ctor();
A = "test";
}
您的 C# 6 代码将如下所示:
public class Test : Base
{
public Test()
{
}
public string A { get; set; } = "test";
}
生成如下 (IL) 代码:
public Test..ctor()
{
<A>k__BackingField = "test";
Base..ctor();
}
请注意,如果您在构造函数中专门初始化 属性,并且有一个 getter/setter 属性,在 C# 6 中它仍然看起来像我的第一段代码上面的答案,而如果你有一个 getter-only 字段,它将看起来像这样:
public Test..ctor()
{
Base..ctor();
<A>k__BackingField = "test";
}
很明显,您的 C# 5 代码看起来像上面的第一段代码,而您的 C# 6 代码看起来像第二段代码。
所以回答你的问题:为什么 C# 5 和 C# 6 在编译自动 属性 初始化方面表现不同?原因是你不能在 C# 5 或更早版本中进行自动 属性 初始化,并且不同的代码编译不同。
I'm curious why prior to C#6 IL uses the property definition to initialize. Is there a specific reason for this?
因为通过自动属性 初始化设置值和在构造函数中设置值是两件不同的事情。他们有不同的行为。
回想一下,属性是环绕字段的访问器方法。所以这一行:
this.First = "Adam";
相当于:
this.set_First("Adam");
你甚至可以在 Visual Studio 中看到这个!尝试在您的 class 中编写一个带有签名 public string set_First(string value)
的方法,然后观察编译器抱怨您踩到它的脚趾。
并且就像方法一样,这些可以在子 classes 中被覆盖。查看此代码:
public class PropertyInitialization
{
public virtual string First { get; set; }
public PropertyInitialization()
{
this.First = "Adam";
}
}
public class ZopertyInitalization : PropertyInitialization
{
public override string First
{
get { return base.First; }
set
{
Console.WriteLine($"Child property hit with the value: '{0}'");
base.First = value;
}
}
}
在此示例中,行 this.First = "Adam"
将调用子 class 中的 setter。因为你正在调用一个方法,还记得吗?如果编译器将此方法调用解释为对支持字段的直接调用,则它最终不会调用子 setter。 编译代码的行为会改变程序的行为。不好!
自动属性不同。让我们通过使用 auto-属性 初始值设定项来更改第一个示例:
public class PropertyInitialization
{
public virtual string First { get; set; } = "Adam";
}
public class ZopertyInitalization : PropertyInitialization
{
public override string First
{
get { return base.First; }
set
{
Console.WriteLine($"Child property hit with the value: '{0}'");
base.First = value;
}
}
}
使用此代码,不会调用子 class 中的 setter 方法。这是故意的。 auto-属性 初始化程序旨在直接设置支持字段。它们看起来和行为都像字段初始值设定项,这就是为什么我们甚至可以在没有 setters 的属性上使用它们,如下所示:
public string First { get; } = "Adam";
这里没有setter方法!我们必须直接访问支持字段才能执行此操作。自动属性允许程序员创建不可变的值,同时仍然能够从良好的语法中受益。