5

I have a function of this kind of shape which does 1 dimensional rootfinding:

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
}

This is a fixed shape in order to make the algorithm reusable. Consider this a fixed library function that I cannot change (or at least needs to be kept generic and reusable and not changed for the specifics of this question).

I'd like to pass in a function which requires external parameters that are Span<T>s held on the stack to avoid allocations, but clearly can't shove Span<T>s into the object since that would require boxing and unboxing.

Using a lambda expression the calling code could look something like:

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>;
}

But this does not work because you can't form a closure over Span<T>s with a lambda expression. They also can't be used in a generic type, any boxing and unboxing is out, can't be passed as a params keyword, can't be on instance variables, etc.

Is there any way to solve this problem?

And just to reinforce that this example is overly simplified. I might have one Span, but the problem I'm currently working on I need to pass in 4 Span's. I need to design for any number of them.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
lamont
  • 3,854
  • 1
  • 20
  • 26
  • 2
    Remember a `Span` represents memory on the stack - so it isn't about boxing, but more to do with the fact the CLR cannot (yet) guarantee that the lambda's closure won't outlive the calling stack-frame. – Dai Jan 06 '20 at 02:09
  • Can't you use `Memory`? – Dai Jan 06 '20 at 02:10
  • I'm using `Span` to specifically avoid heap allocations, `Memory` is on the heap – lamont Jan 06 '20 at 02:29
  • Could you use `Memory` + `ArrayPool` to avoid new allocations? – yaakov Jan 06 '20 at 04:13
  • I don't think you can avoid a memory allocation happening with a lambda. What about defining your own `ValueType` & `Fun` implementation, to control the lifetime yourself? – Jeremy Lakeman Jan 06 '20 at 04:47

1 Answers1

2

Spans can't be used in captured variables. Pointers can, however. This might obviate the nicest features of spans, but in the general case, you can fix the span, and capture that fixed pointer, but you need to be really really careful to make sure that the delegate invocation can't escape the fixed block, i.e. you're not handing the delegate out anywhere. Once you have the fixed pointer, you can recreate the span internally:

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);
}

In your specific case, however, you might as well not bother with the original span at all, if you're just going to pin it (when it is already effectively fixed on the stack). The "don't let the delegate outlive the stack-frame" requirement still remains, but the compiler is slightly less shouty about it because you're already breaking all the rules:

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

Both of these require unsafe modifiers, obviously.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900