15

我注意到即使未初始化局部变量,以下内容也会编译和执行。这是 Span 的一个特性吗?

void Uninitialized()
{
  Span<char> s1;
  var l1 = s1.Length;

  Span<char> s2;
  UninitializedOut(out s2);
  var l2 = s2.Length;
}

void UninitializedOut(out Span<char> s)
{}
4

3 回答 3

18

这看起来像是由引用程序集引起的问题,由于Span<T>具有特定于框架的内部结构的方式,这是必需的。

这意味着在参考程序集中:没有字段(编辑:这不完全正确-见脚注)。

如果分配了所有字段,则认为Astruct已分配(出于“明确分配”的目的),在这种情况下,编译器会看到“已分配零字段中的所有零:一切正常 - 已分配此变量”。但是编译器似乎并不知道实际的字段,所以它被误导允许一些技术上无效的东西。

你绝对不应该依赖这个表现得很好!虽然在大多数情况下.locals init应该意味着你实际上并没有得到任何可怕的东西。然而,目前有一些工作正在进行中,以允许人们在某些情况下进行压制 .locals init——我害怕认为在这种情况下会发生什么——尤其是因为Span<T>它的工作原理很像a——如果该领域真的不是ref T,那可能会变得非常非常危险初始化为零。

有趣的是,它可能已经修复了:请参阅Sharplab 上的这个示例。或者,也许Sharplab 正在使用具体的目标框架,而不是参考程序集。


编辑:很奇怪,如果我将参考组件加载到ildasm反射器中,我可以看到:

.field private initonly object _dummy

这是参考程序集中的欺骗字段,旨在阻止这种情况发生,但是......现在看起来它的工作不太可靠!


更新:显然这里的区别是一个微妙但已知的编译器问题,出于兼容性原因仍然存在;结构的明确分配考虑了本地已知类型的私有字段,但不考虑外部程序集中类型的私有引用类型字段。

于 2018-04-04T14:16:55.517 回答
16

马克有一个很好的答案。我想详细说明一下历史/背景。

首先,这绝对是一个编译器错误。根据明确分配的规则,这个本地不是明确分配的,任何使用都应该是错误的。不幸的是,由于多种原因,这个错误很难修复:

  • 这个错误很老,至少可以追溯到 C# 4.0。这让客户在不经意间依赖它 7 年以上
  • BCL 中有许多具有这种基本结构的结构。例如CancellationToken

这些加在一起意味着修复这个问题可能会破坏大量现有代码。尽管如此,C# 团队试图在 C# 6.0 中修复该错误时,该错误还很年轻。但是尝试使用此修复程序编译 Visual Studio 源代码表明,客户对依赖此错误的担忧是有根据的:存在许多构建中断。足以说服我们它会对大量代码产生负面影响。因此修复被撤销。

这里的第二个问题是所有编译器团队成员都不知道这个错误(至少在今天之前)。自从修复被撤消以来已经有大约 3 年了,并且从那时起有一点翻身。验证我们如何为其生成参考程序集的团队成员Span<T>并未意识到此错误,并根据语言规范推荐了当前设计。我是那些开发人员之一:(

仍在讨论这个问题,但很可能我们会更新Span<T>, 和其他类型的参考汇编策略,以便避免这个编译器错误。

感谢您报告此事。很抱歉造成的混乱:(

于 2018-04-05T03:08:27.843 回答
2

这或多或少是设计使然,因为它在很大程度上取决于底层struct本身是否包含任何字段。

此代码编译例如:

public struct MySpan<T>
{
    public int Length => 1;
}

static class Program
{
    static void Main(string[] args)
    {
        MySpan<char> s1;
        var l1 = s1.Length;
    }
}

但是这段代码没有:

public struct MySpan<T>
{
    public int Length { get; }
}

static class Program
{
    static void Main(string[] args)
    {
        MySpan<char> s1;
        var l1 = s1.Length;
    }
}

似乎在这种情况下,结构是默认的,这就是为什么它不会抱怨缺少分配。正如 Marc 的回答中所解释的,它没有检测到任何字段是一个错误。

于 2018-04-04T14:13:56.920 回答