"No Operation" Delegate in c#

Viewed 241

I've a generic class like

public class Foo<T> where T : Delegate {
    private T nop;
    public Foo(T nop) {
        this.nop = nop;
    }
    public T BuildDelegateChain() {
        if(chainAvailable) {
           return Delegate.Combine(...) as T;
        } else {
           return  nop;
        }
    }
    ....
}

So for each instance I have to call:

Foo<Action<int>> foo = new Foo<Action<int>>( _ =>{});
Foo<Action<int,int>> foo = new Foo<Action<int,int>>( (_,__) =>{});

Is there a way to get a default "No Operation" delegate matching the type T? I'm looking for something to replace the constructor to a constructor without any argument. Something like:

...
public Foo() {
    this.nop = Delegate.CreateNop(typeof(T));
}   
...
1 Answers

You can create a no-op delegate dynamically using the Expression class. For delegates with return types, the no-op would have to return default(TReturn). Otherwise it just needs to be an empty block. We'll then cache the delegate so reflection only happens once.

using System.Linq;
using System.Linq.Expressions;

public static class DelegateHelper<T> where T : Delegate
{
    public static T NoOp { get; } = BuildNoOpDelegate();

    private static T BuildNoOpDelegate()
    {
        var invoke = typeof(T).GetMethod(nameof(Action.Invoke));

        var paramTypes = invoke.GetParameters().Select(c => c.ParameterType);
        
        // return default(TReturn) or default(Void)
        var body = Expression.Default(invoke.ReturnType);

        var lambda = Expression.Lambda<T>(
            body,
            paramTypes.Select(Expression.Parameter)
        );
        return lambda.Compile();
    }

}

And then to use it:

public class Foo<T> where T : Delegate {
    private T nop;
    public Foo() {
        nop = DelegateHelper<T>.NoOp;
    }
}

Interestingly, this also works for delegates with out/ref parameters. In the case of out we don't need to do anything special to set the values before the method exits (which would be required with straight C#)--they keep their default values. For example, suppose a delegate with the following signature:

public delegate int MyDelegate(out int x, ref string y);

This code will compile and execute successfully:

var nop = DelegateHelper<MyDelegate>.NoOp;

string val = "hello";
int ret = nop(out int i, ref val);

Console.WriteLine(ret); // prints "0" 
Console.WriteLine(i); // prints "0"
Console.WriteLine(val); // unchanged, prints "hello"

Now this is all probably a bit overkill, but it should answer your question. Alternatively you could just treat null as your no-op sentinel value and use whatever?.Invoke() instead.

Related