在C#6之前,属性的初始化没有使用支持字段来初始化默认值。 在C#6中,它使用支持字段来初始化新的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();
}
}
答案 0 :(得分:9)
我很好奇为什么在C#6 IL之前使用属性定义进行初始化。这是否有特定的原因?
因为通过自动属性初始化设置值并在构造函数中设置值是两回事。他们有不同的行为。
回想一下,属性是包含字段的访问器方法。所以这一行:
this.First = "Adam";
相当于:
this.set_First("Adam");
您甚至可以在Visual Studio中看到这一点!尝试在你的班级中编写一个带有签名public string set_First(string value)
的方法,并观察编译器抱怨你踩到它的脚趾。
就像方法一样,这些可以在子类中重写。看看这段代码:
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"
将调用子类中的setter。因为你正在调用一种方法,还记得吗?如果编译器将此方法调用解释为对支持字段的直接调用,则它不会最终调用子setter。 编译代码的行为会改变程序的行为。不好!
自动属性不同。让我们使用自动属性初始值设定项来更改第一个示例:
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;
}
}
}
使用此代码,不会调用子类中的setter方法。这是故意的。自动属性初始值设定项用于直接设置支持字段。它们的外观和行为类似于字段初始化器,这就是为什么我们甚至可以在没有setter的属性上使用它们,如下所示:
public string First { get; } = "Adam";
这里没有setter方法!我们必须直接访问支持字段才能执行此操作。自动属性允许程序员创建不可变值,同时仍然可以从良好的语法中受益。
答案 1 :(得分:1)
它唯一能产生影响的是,如果属性设置器的效果比简单设置值更多。对于自动实现的属性,唯一可能发生的时间是它们是virtual
并被覆盖。在这种情况下,在基类构造函数运行之前调用派生类方法是一个非常糟糕的主意。 C#经历了很多麻烦,以确保您不会意外地结束对尚未完全初始化的对象的引用。所以它必须直接设置字段以防止这种情况。
答案 2 :(得分:1)
请记住,未在构造函数中设置属性的默认值(您的代码显示:assigments,然后是构造函数)。
现在,C#规范说自动初始化值是在构造函数之前设置的。这是有道理的:当在构造函数中再次设置这些值时,它们将被覆盖。
现在 - 在调用构造函数之前 - 没有初始化的getter和setter方法。它们应该如何使用?
这就是为什么(通过当时未初始化的后备字段)直接初始化。
正如hvd所提到的,虚拟调用也会出现问题,但是它们甚至没有被初始化是主要原因。
如果在构造函数中指定值,它的行为仍然与以前相同:
Example with property that is autoinitialized and changed in the ctor
为什么不对此进行优化?
有关此主题的信息,请参阅my question:
但是不应该优化它吗?
它可能,但只有当该类不从另一个继承时 在其构造函数中使用该值的类,它知道它是一个 自动属性和setter没有做任何其他事情。
这将是很多(危险的)假设。编译器需要 在进行这样的优化之前检查很多东西。
旁注:
我假设您使用某种工具来查看编译器生成的c#代码 - 它并不完全准确。对于构造函数生成的IL代码没有准确的表达式 - ctor不是IL中的方法,它的东西是不同的。为了便于理解,我们可以假设它是相同的。
http://tryroslyn.azurewebsites.net/例如有这样的评论:
// This is not valid C#, but it represents the IL correctly.
答案 3 :(得分:1)
您可以获得所示代码的一种方法是使用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或之前进行自动属性初始化,并且不同的代码编译方式不同。
答案 4 :(得分:0)
我假设您的C#5.0代码如下所示:
class C
{
public C()
{
First = "Adam";
}
public string First { get; private set; }
}
然后在C#6.0中,您所做的唯一更改是将First
设为get
- 仅限autoproperty:
class C
{
public C()
{
First = "Adam";
}
public string First { get; }
}
在C#5.0案例中,First
是一个带有setter的属性,您可以在构造函数中使用它,因此生成的IL反映了这一点。
在C#6.0版本中,First
没有setter,因此构造函数必须直接访问支持字段。
这两种情况对我来说都很有意义。