下面的“修复”让我很困惑;这里的场景是根据大小有条件地决定是使用堆栈还是租用缓冲区 - 这是一个非常利基但有时是必要的优化,但是:使用“明显”的实现(第 3 号,推迟明确的分配,直到我们真正想要分配它),编译器向 CS8353 抱怨:
'Span<int>' 类型的 stackalloc 表达式的结果不能在此上下文中使用,因为它可能暴露在包含方法之外
简短的复制(完整的复制如下)是:
// take your pick of:
// Span<int> s = stackalloc[0]; // works
// Span<int> s = default; // fails
// Span<int> s; // fails
if (condition)
{ // CS8353 happens here
s = stackalloc int[size];
}
else
{
s = // some other expression
}
// use s here
我在这里唯一能想到的是,编译器确实在标记stackalloc
正在转义stackalloc
发生事件的上下文,并且挥舞着一个标记说“我无法证明这在该方法的后面是否安全” ,但是通过stackalloc[0]
在开始时,我们将“危险”上下文范围推得更高,现在编译器很高兴它永远不会逃脱“危险”范围(即它实际上从未离开方法,因为我们正在声明在顶部范围内)。这种理解是否正确,就可以证明的内容而言,这只是编译器的限制?
(对我来说)真正有趣的是,从根本上讲,无论如何= stackalloc[0]
都是无操作的,这意味着至少在编译后的形式中,工作数字 1与失败的数字 2 相同。= stackalloc[0]
= default
完整再现(也可在 SharpLab 上查看 IL)。
using System;
using System.Buffers;
public static class C
{
public static void StackAllocFun(int count)
{
// #1 this is legal, just initializes s as a default span
Span<int> s = stackalloc int[0];
// #2 this is illegal: error CS8353: A result of a stackalloc expression
// of type 'Span<int>' cannot be used in this context because it may
// be exposed outside of the containing method
// Span<int> s = default;
// #3 as is this (also illegal, identical error)
// Span<int> s;
int[] oversized = null;
try
{
if (count < 32)
{ // CS8353 happens at this stackalloc
s = stackalloc int[count];
}
else
{
oversized = ArrayPool<int>.Shared.Rent(count);
s = new Span<int>(oversized, 0, count);
}
Populate(s);
DoSomethingWith(s);
}
finally
{
if (oversized is not null)
{
ArrayPool<int>.Shared.Return(oversized);
}
}
}
private static void Populate(Span<int> s)
=> throw new NotImplementedException(); // whatever
private static void DoSomethingWith(ReadOnlySpan<int> s)
=> throw new NotImplementedException(); // whatever
// note: ShowNoOpX and ShowNoOpY compile identically just:
// ldloca.s 0, initobj Span<int>, ldloc.0
static void ShowNoOpX()
{
Span<int> s = stackalloc int[0];
DoSomethingWith(s);
}
static void ShowNoOpY()
{
Span<int> s = default;
DoSomethingWith(s);
}
}