我注意到即使未初始化局部变量,以下内容也会编译和执行。这是 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)
{}
我注意到即使未初始化局部变量,以下内容也会编译和执行。这是 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)
{}
这看起来像是由引用程序集引起的问题,由于Span<T>
具有特定于框架的内部结构的方式,这是必需的。
这意味着在参考程序集中:没有字段(编辑:这不完全正确-见脚注)。
如果分配了所有字段,则认为Astruct
已分配(出于“明确分配”的目的),在这种情况下,编译器会看到“已分配零字段中的所有零:一切正常 - 已分配此变量”。但是编译器似乎并不知道实际的字段,所以它被误导允许一些技术上无效的东西。
你绝对不应该依赖这个表现得很好!虽然在大多数情况下.locals init
应该意味着你实际上并没有得到任何太可怕的东西。然而,目前有一些工作正在进行中,以允许人们在某些情况下进行压制 .locals init
——我害怕认为在这种情况下会发生什么——尤其是因为Span<T>
它的工作原理很像a——如果该领域真的不是ref T
,那可能会变得非常非常危险初始化为零。
有趣的是,它可能已经修复了:请参阅Sharplab 上的这个示例。或者,也许Sharplab 正在使用具体的目标框架,而不是参考程序集。
编辑:很奇怪,如果我将参考组件加载到ildasm
反射器中,我可以看到:
.field private initonly object _dummy
这是参考程序集中的欺骗字段,旨在阻止这种情况发生,但是......现在看起来它的工作不太可靠!
更新:显然这里的区别是一个微妙但已知的编译器问题,出于兼容性原因仍然存在;结构的明确分配考虑了本地已知类型的私有字段,但不考虑外部程序集中类型的私有引用类型字段。
马克有一个很好的答案。我想详细说明一下历史/背景。
首先,这绝对是一个编译器错误。根据明确分配的规则,这个本地不是明确分配的,任何使用都应该是错误的。不幸的是,由于多种原因,这个错误很难修复:
这些加在一起意味着修复这个问题可能会破坏大量现有代码。尽管如此,C# 团队试图在 C# 6.0 中修复该错误时,该错误还很年轻。但是尝试使用此修复程序编译 Visual Studio 源代码表明,客户对依赖此错误的担忧是有根据的:存在许多构建中断。足以说服我们它会对大量代码产生负面影响。因此修复被撤销。
这里的第二个问题是所有编译器团队成员都不知道这个错误(至少在今天之前)。自从修复被撤消以来已经有大约 3 年了,并且从那时起有一点翻身。验证我们如何为其生成参考程序集的团队成员Span<T>
并未意识到此错误,并根据语言规范推荐了当前设计。我是那些开发人员之一:(
仍在讨论这个问题,但很可能我们会更新Span<T>
, 和其他类型的参考汇编策略,以便避免这个编译器错误。
感谢您报告此事。很抱歉造成的混乱:(
这或多或少是设计使然,因为它在很大程度上取决于底层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 的回答中所解释的,它没有检测到任何字段是一个错误。