不需要分配结构类型的out参数

时间:2019-10-30 18:47:29

标签: c# .net

在代码检查过程中意外注释掉函数中的一行时,我注意到代码中有些奇怪的行为。复制非常困难,但我将在此处描述一个类似的示例。

我有这个测试班:

#cf img {
  position: absolute;
  left: 0;
  -webkit-transition: opacity 1s ease-in-out;
  -moz-transition: opacity 1s ease-in-out;
  -o-transition: opacity 1s ease-in-out;
  transition: opacity 1s ease-in-out;
}

#cf img.top:hover {
  opacity: 0;
}

<div id="cf"> <img class="bottom" src="https://cdn4.iconfinder.com/data/icons/imoticons/105/imoticon_12-128.png" /> <img class="top" src="https://cdn4.iconfinder.com/data/icons/imoticons/105/imoticon_15-128.png" /> </div>中没有分配给电子邮件,通常会引发错误:

  

必须在控制离开当前方法之前将out参数'email'分配给

但是,如果EmailAddress位于单独程序集中的结构中,则不会产生错误,并且一切都可以编译。

public class Test
{
    public void GetOut(out EmailAddress email)
    {
        try
        {
            Foo(email);
        }
        catch
        {
        }
    }

    public void Foo(EmailAddress email)
    {
    }
}

为什么编译器不强制必须将电子邮件分配给? 如果在单独的程序集中创建该结构,为什么会编译该代码,但如果在现有程序集中定义该结构,则为什么不编译?

2 个答案:

答案 0 :(得分:12)

TLDR:这是一个长期存在的已知错误。我在2010年首次写了这篇文章:

https://blogs.msdn.microsoft.com/ericlippert/2010/01/18/a-definite-assignment-anomaly/

它是无害的,您可以放心地忽略它,并祝贺您发现了一个不太明显的错误。

  

为什么编译器不强制必须明确分配Email

哦,确实如此。就像我们将看到的那样,它只是错误地认为什么条件意味着必须明确分配变量。

  

如果结构是在单独的程序集中创建的,为什么此代码会编译,而如果结构是在现有程序集中定义的,为什么不编译?

这就是漏洞的症结所在。该错误是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的变量的别名,因此存储已被分配,并且至少处于从存储分配器中出来的状态。如果调用方在变量中放置了一个值,则该值在那里。

我们需要什么?我们要求在控制正常离开f的任何位置明确分配M。所以您会期望像这样:

void M(out Foo f)
{
  f = new Foo();
}

f.xf.y设置为其默认值。但是呢?

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的有效值,所以我们只需设置字段,就可以了,对吧?

对。那是什么错误?

您发现的错误是:为节省成本,C#编译器不会为引用库中的结构的私有字段加载元数据。该元数据可能巨大,并且会降低编译器的速度,从而几乎无法每次将其全部加载到内存中。

现在您应该可以推断出发现的错误的原因。当编译器检查是否明确指定了out参数时,它将已知字段的数量与明确初始化的字段数量进行比较,在您的情况下,它仅知道公共字段,因为未加载私有字段元数据。编译器得出结论:“需要零字段,已初始化零字段,我们很好。”

就像我说的那样,这个错误已经存在了十多年了,像您这样的人偶尔会重新发现并报告它。它是无害的,而且不太可能得到修复,因为修复它几乎没有什么好处,但性能成本很高。

当然,该错误不会针对您项目中源代码中结构的私有字段进行复制,因为显然编译器已经掌握了有关私有字段的信息。

答案 1 :(得分:1)

虽然它看起来像一个错误,但确实有一定道理。

仅在使用类库时出现“丢失错误”。一个类库可能已经用另一种.net语言编写,例如VB.Net。 “确定分配跟踪”是C#的功能,而不是框架的功能。

因此,总的来说,我不认为这是一个错误,但我不知道有关此问题的权威性声明。