对于无法将 Span<T> 传递到 lambda 表达式的有效替代方案是什么?

lam*_*ont 5 c# system.memory

我有一个这种形状的函数,可以进行一维求根:

public delegate double Fun(double x, object o);

public static void Solve(Fun f, out double y, object o) 
{
    y = f(1.0, o);  // all the irrelevant details of the algorithm omitted
}
Run Code Online (Sandbox Code Playgroud)

这是一个固定的形状,以便使算法可重用。将此视为我无法更改的固定库函数(或者至少需要保持通用性和可重用性,并且不会针对此问题的具体情况进行更改)。

我想传递一个函数,该函数需要Span<T>保存在堆栈上的外部参数以避免分配,但显然不能将Span<T>其推入对象,因为这需要装箱和拆箱。

使用 lambda 表达式,调用代码可能类似于:

void CallingMethod()
{
   Span<double> k1  = stackalloc double[n];
   double answer;
   Solve((x, o) => Wrapper(x, k1, o), out answer, null);
}

double Wrapper(double x, ReadOnlySpan<double> k1, object o)
{
   return <some function of x and k1>;
}
Run Code Online (Sandbox Code Playgroud)

但这不起作用,因为您无法Span<T>使用 lambda 表达式在 s 上形成闭包。它们也不能在泛型类型中使用,任何装箱和拆箱都已取消,不能作为 params 关键字传递,不能用于实例变量等。

有什么办法可以解决这个问题吗?

只是为了强调这个例子过于简单了。我可能有一个 Span,但我目前正在解决的问题需要传递 4 个 Span。我需要为任意数量的人进行设计。

Mar*_*ell 2

Span 不能在捕获的变量中使用。然而,指针可以。这可能会消除跨度最好的功能,但在一般情况下,您可以修复跨度并捕获该固定指针,但您需要非常小心以确保委托调用无法逃脱fixed块,即您不会将代表分发到任何地方。一旦有了固定指针,就可以在内部重新创建跨度:

Span<double> k1 = // ...
// ...
fixed (double* ptr = k1)
{
    var evil = ptr; // this is me convincing the compiler that
                    // I've considered whether the delegate could
                    // outlive the delegate invocation
    Solve((x, o) => Wrapper(x, new Span<double>(evil, k1.Length), o), out answer, null);
}
Run Code Online (Sandbox Code Playgroud)

然而,在您的具体情况下,如果您只是要固定它(当它已经有效地固定在堆栈上时),您可能根本不关心原始跨度。“不要让委托超出堆栈框架”的要求仍然存在,但编译器对此稍微不那么大声,因为您已经违反了所有规则:

double* k1 = stackalloc double[n];
// ...
Solve((x, o) => Wrapper(x, new Span<double>(k1, n), o), out answer, null);
Run Code Online (Sandbox Code Playgroud)

显然,这两者都需要unsafe修饰符。