Search code examples
c#.netunsafe

Returning data from a static (non-capturing) lambda


Consider the following method:

static void Invoke<TArg>(Action<TArg> action, TArg arg) => action(arg);

I know; it seems useless, but it's a simplification of the actual code.

Anyway, I need to call Invoke from a method with by-reference parameters, passing a lambda for action. I need some way to return values for the by-reference parameters from inside that lambda. Finally, the lambda must be static (non-capturing).

I've come up with the following wacky scheme:

static unsafe void UpdateFooAndGetBar(ref object foo, out object bar) {
    var args = (foo, bar: (object)null);
    Invoke(
        static pArgs => {
            ref var args = ref Unsafe.AsRef<(object, object)>(pArgs.ToPointer());
            args.Item1 = 123;
            args.Item2 = "baz";
        },
        new IntPtr(Unsafe.AsPointer(ref args))
    );
    foo = args.foo;
    bar = args.bar;
}

That seems to work, but Unsafe makes me nervous. So, is my code legit? Is there a better way?


Solution

  • Let's assume we have a custom delegate that allows by-ref usage:

    delegate void ActionByRef<T>(ref T value);
    

    We can then use that in our Invoke:

    static void Invoke<T>(ActionByRef<T> callback, ref T args)
        => callback(ref args); // TODO any other logic
    

    and consume it without any unmanaged pointers:

    public static void UpdateFooAndGetBar(ref object foo, out object bar)
    {
        var args = (foo, bar: (object)null);
        Invoke(
            static (ref (object foo, object bar) args) => {
                args.Item1 = 123;
                args.Item2 = "baz";
            },
            ref args
        );
        foo = args.foo;
        bar = args.bar;
    }
    

    You could also take this a step further by having a custom delegate that matches your exact needs, but that is less reusable:

    delegate void RenameThis(ref object foo, out object bar);
    //...
    static void Invoke(RenameThis callback, ref object foo, out object bar)
        => callback(ref foo, out bar);
    //...
    public static void UpdateFooAndGetBar(ref object foo, out object bar)
    {
        Invoke(
            static (ref object foo, out object bar) => {
                foo = 123;
                bar = "baz";
            },
            ref foo,
            out bar
        );
    }