Search code examples
c#linqlinq-expressions

Possible to use LINQ Expression with Delegate Signature to obtain parameters?


Is it possible to obtain ParameterInfo and argument values for a delegate invocation which is inside a (compiler-generated) LambdaExpression?

For example I have defined a delegate:

public class A
{
   public delegate string[] GetInfo(int[] ids);
}

Now I need to use an expression to obtain the parameter values with the PropertyInfo of the delegate parameters. Only way I was able to get the Expression to work is to use it as an Action:

public readonly struct DelegateInfo
{
    public Type ReturnType{ get; }

    public Dictionary<ParameterInfo, object> Parameters { get; }
}

public class B
{
    public B()
    {
        var info = GetDelegateInfo<A.GetInfo>(del => del(new int[]{1,2,3}));
    }

    public DelegateInfo GetDelegateInfo<TDelegate>(Expression<Action<TDelegate>> exp)
        where TDelegate : Delegate
    {
        // Get the action of the expression
        var action = exp.Parameters[0];

        // How to obtain the delegate ParameterInfo and values?
        ...

        return new DelegateInfo { ... };
    }
}

Is this the right way to achieve this? Is there a better way?


Solution

  • With updated struct to:

    public readonly struct DelegateInfo
    {
        public Type ReturnType { get; init; }
    
        public Dictionary<ParameterInfo, object> Parameters { get; init; }
    }
    

    The main trick is to get the Invoke method of the delegate and do something like:

    DelegateInfo GetDelegateInfo<TDelegate>(Expression<Action<TDelegate>> exp)
        where TDelegate : Delegate
    {
        var type = typeof(TDelegate); // or exp.Parameters[0].Type
        var methodInfo = type.GetMethod("Invoke"); // get the invoke method
    
        // TODO: throw error if not an InvocationExpression
        var invocationExpression = exp.Body as InvocationExpression;
        return new DelegateInfo
        {
            ReturnType = methodInfo.ReturnType,
            Parameters = methodInfo.GetParameters()
                .Zip(invocationExpression.Arguments) // get passed arguments
                .ToDictionary(t => t.Item1, t =>
                {
                    var argument = t.Item2;
                    // quick and dirty approach - compile and invoke
                    // possibly better to visit the expression
                    // but will require much more work
                    return Expression.Lambda<Func<object>>(argument).Compile()();
                })
        };
    }