Span<T> 不需要局部变量赋值。那是一个功能吗?
Span<T> does not require local variable assignment. Is that a feature?
我注意到即使局部变量没有初始化,下面的代码也会编译和执行。这是 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)
{}
这或多或少是设计使然,因为它在很大程度上取决于基础 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 的回答中所解释的,它没有检测到任何字段 是 一个错误。
这看起来像是引用程序集引起的问题,由于 Span<T>
具有 framework-specific 内部结构的方式而需要。
这意味着在参考程序集中:没有字段(编辑:这不完全正确 - 见脚注) .
如果所有字段都已分配,则 A struct
被视为已分配(出于 "definite assignment" 的目的),在这种情况下,编译器会看到 "all zero of zero fields have been assigned: all good - this variable is assigned"。但是编译器似乎并不知道 actual 字段,因此它被误导允许某些技术上无效的内容。
你绝对不应该依赖这个表现得很好!虽然在大多数情况下 .locals init
应该意味着你实际上并没有得到任何 太 可怕的东西。但是, 目前正在进行一些工作,以允许人们在某些情况下 suppress .locals init
- 我不敢想象会发生什么在这里的场景中——特别是因为Span<T>
的工作方式很像ref T
——如果字段确实没有初始化为零。
有趣的是,它 可能已经修复 :参见 this example on sharplab。或者,也许 sharplab 使用的是具体的目标框架,而不是参考程序集。
编辑:很奇怪,如果我将参考程序集加载到 ildasm
或反射器中,我可以看到:
.field private initonly object _dummy
这是参考程序集中的欺骗字段,旨在 阻止这种情况发生,但是......看起来它现在工作得不是很可靠!
更新:显然这里的区别是 subtle but known compiler issue 出于兼容性原因而保留;结构的明确赋值考虑本地已知类型的私有字段,但不考虑私有reference-type 外部程序集中的类型字段。
Marc 的回答很棒。我想详细说明一下历史/背景。
首先这绝对是一个compiler bug。根据明确赋值的规则,这个局部变量不是明确赋值的,任何用法都应该是错误的。不幸的是,这个错误很难修复,原因有很多:
- 这个错误是旧的,至少可以追溯到 C# 4.0。这让客户有 7 年以上的时间不经意地依赖它
- BCL 中有许多结构体都具有这种基本结构。例如 CancellationToken.
这些加在一起意味着解决这个问题可能会破坏大量现有代码。尽管如此,C# 团队还是尝试在 C# 6.0 中修复该错误,当时该错误还很年轻。但是尝试使用此修复程序编译 Visual Studio 源代码表明,围绕客户依赖此错误的担忧是有根据的:存在许多构建中断。足以让我们相信它会对大量代码产生负面影响。因此修复被撤消。
这里的第二个问题是所有编译器团队成员都不知道这个错误(至少在今天之前)。自从修复被撤消以来已经过去了大约 3 年,从那以后有了一些转变。验证我们如何为 Span<T>
生成参考程序集的团队成员并不知道这个错误,并根据语言规范推荐了当前的设计。我是那些开发人员之一:(
仍在讨论这个问题,但很可能我们将更新 Span<T>
和其他类型的参考汇编策略,以避免此编译器错误。
感谢您报告此事。很抱歉造成的混乱:(
我注意到即使局部变量没有初始化,下面的代码也会编译和执行。这是 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)
{}
这或多或少是设计使然,因为它在很大程度上取决于基础 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 的回答中所解释的,它没有检测到任何字段 是 一个错误。
这看起来像是引用程序集引起的问题,由于 Span<T>
具有 framework-specific 内部结构的方式而需要。
这意味着在参考程序集中:没有字段(编辑:这不完全正确 - 见脚注) .
如果所有字段都已分配,则A struct
被视为已分配(出于 "definite assignment" 的目的),在这种情况下,编译器会看到 "all zero of zero fields have been assigned: all good - this variable is assigned"。但是编译器似乎并不知道 actual 字段,因此它被误导允许某些技术上无效的内容。
你绝对不应该依赖这个表现得很好!虽然在大多数情况下 .locals init
应该意味着你实际上并没有得到任何 太 可怕的东西。但是, 目前正在进行一些工作,以允许人们在某些情况下 suppress .locals init
- 我不敢想象会发生什么在这里的场景中——特别是因为Span<T>
的工作方式很像ref T
——如果字段确实没有初始化为零。
有趣的是,它 可能已经修复 :参见 this example on sharplab。或者,也许 sharplab 使用的是具体的目标框架,而不是参考程序集。
编辑:很奇怪,如果我将参考程序集加载到 ildasm
或反射器中,我可以看到:
.field private initonly object _dummy
这是参考程序集中的欺骗字段,旨在 阻止这种情况发生,但是......看起来它现在工作得不是很可靠!
更新:显然这里的区别是 subtle but known compiler issue 出于兼容性原因而保留;结构的明确赋值考虑本地已知类型的私有字段,但不考虑私有reference-type 外部程序集中的类型字段。
Marc 的回答很棒。我想详细说明一下历史/背景。
首先这绝对是一个compiler bug。根据明确赋值的规则,这个局部变量不是明确赋值的,任何用法都应该是错误的。不幸的是,这个错误很难修复,原因有很多:
- 这个错误是旧的,至少可以追溯到 C# 4.0。这让客户有 7 年以上的时间不经意地依赖它
- BCL 中有许多结构体都具有这种基本结构。例如 CancellationToken.
这些加在一起意味着解决这个问题可能会破坏大量现有代码。尽管如此,C# 团队还是尝试在 C# 6.0 中修复该错误,当时该错误还很年轻。但是尝试使用此修复程序编译 Visual Studio 源代码表明,围绕客户依赖此错误的担忧是有根据的:存在许多构建中断。足以让我们相信它会对大量代码产生负面影响。因此修复被撤消。
这里的第二个问题是所有编译器团队成员都不知道这个错误(至少在今天之前)。自从修复被撤消以来已经过去了大约 3 年,从那以后有了一些转变。验证我们如何为 Span<T>
生成参考程序集的团队成员并不知道这个错误,并根据语言规范推荐了当前的设计。我是那些开发人员之一:(
仍在讨论这个问题,但很可能我们将更新 Span<T>
和其他类型的参考汇编策略,以避免此编译器错误。
感谢您报告此事。很抱歉造成的混乱:(