我注意到我的代码中有一些奇怪的行为,当我在代码审查过程中不小心注释掉了函数中的一行时。它很难复制,但我将在这里描绘一个类似的例子。
我有这个测试班:
public class Test
{
public void GetOut(out EmailAddress email)
{
try
{
Foo(email);
}
catch
{
}
}
public void Foo(EmailAddress email)
{
}
}
GetOut
中没有分配给电子邮件,这通常会引发错误:
在控件离开当前方法之前,必须将out参数'email'分配给
但是,如果EmailAddress位于单独程序集中的结构中,则不会创建错误,并且一切都可以编译。
public struct EmailAddress
{
#region Constructors
public EmailAddress(string email)
: this(email, string.Empty)
{
}
public EmailAddress(string email, string name)
{
this.Email = email;
this.Name = name;
}
#endregion
#region Properties
public string Email { get; private set; }
public string Name { get; private set; }
#endregion
}
为什么编译器不强制要求必须将电子邮件分配给?为什么如果结构是在单独的程序集中创建的,则此代码会编译,而如果结构在现有程序集中定义,则不编译?
TLDR:这是一个长期存在的已知错误。我第一次写这篇文章是在2010年:
https://blogs.msdn.microsoft.com/ericlippert/2010/01/18/a-definite-assignment-anomaly/
它是无害的,你可以放心地忽略它,并祝贺自己发现了一个有点晦涩的bug。
为什么编译器不强制要求必须明确分配
哦,确实如此。正如我们将要看到的,它只是对什么条件意味着变量被明确赋值有一个错误的想法。
为什么如果结构是在单独的程序集中创建的,则此代码会编译,而如果结构在现有程序集中定义,则不编译?
这就是bug的症结所在。该错误是C#编译器如何对结构进行确定赋值检查和编译器如何从库加载元数据的交叉结果。
考虑一下:
struct Foo
{
public int x;
public int y;
}
// Yes, public fields are bad, but this is just
// to illustrate the situation.
void M(out Foo f)
{
好吧,在这一点上,我们知道什么?f
是类型为Foo
的变量的别名,因此存储已经被分配,并且肯定至少处于从存储分配器出来的状态。如果调用方在变量中放置了一个值,那么该值就是存在的。
我们需要什么?我们要求在控制正常离开M
的任何点上明确地分配f
。所以你会期待这样的东西:
void M(out Foo f)
{
f = new Foo();
}
其将CCD_ 7和CCD_。但是这个呢?
void M(out Foo f)
{
f = new Foo();
f.x = 123;
f.y = 456;
}
这也应该没问题。但是,关键是,为什么我们需要分配默认值,只是为了稍后将其吹走C#的确定赋值检查器检查每个字段是否被赋值!这是合法的:
void M(out Foo f)
{
f.x = 123;
f.y = 456;
}
为什么这不合法?这是一种值类型。f
是一个变量,它已经包含一个类型为Foo
的有效值,所以让我们只设置字段,我们就完成了,对吧?
对。那么这个bug是什么呢?
您发现的错误是:为了节省成本,C#编译器不加载引用库中结构的私有字段的元数据。该元数据可能是巨大的,并且每次都将其全部加载到内存中会减慢编译器的速度,但收效甚微。
现在,您应该能够推断出您发现的错误的原因。当编译器检查out参数是否被明确分配时,会将已知字段的数量与被明确初始化的字段的数量进行比较,在您的情况下,它只知道零公共字段,因为没有加载专用字段元数据。编译器得出结论:"需要零字段,初始化零字段,我们很好。">
正如我所说,这个错误已经存在了十多年,像你这样的人偶尔会重新发现并报告它。它是无害的,而且不太可能修复,因为修复它几乎没有任何好处,但性能成本很高。
当然,这个错误不会重新处理项目中源代码中结构的私有字段,因为编译器显然已经掌握了有关私有字段的信息。