通过表达式选择方法时,没有重载匹配委托 'Action'

No Overload matches delegate 'Action' when selecting method via an expression

我正在尝试创建一个流畅的 API 允许 selecting class 方法。尝试 select 带有参数的方法时,出现错误:

No overload for 'MethodB' matches delegate 'Action'.

C# 似乎没有自动确定正确的泛型方法。如果我在调用签名中指定泛型类型,它会起作用,但如果省略,则不会。通常,如果我将此技术用于 select 属性或字段,泛型类型将自动确定,但我怀疑 C#(或 Visual Studio)无法自动确定类型,因为它是包裹在 Action<>.

有没有一种方法可以确保调用此方法不需要声明显式泛型?如果没有,我很想知道为什么。

public class Program
{

  public static void Main( string[] args )
  {
    var builder = new OperationBuilder<SomeClass>();
    builder.AddMethod( x => x.MethodA );
    builder.AddMethod( x => x.MethodB ); // Gives error
    builder.AddMethod<double>( x => x.MethodB ); // Works correctly
  }


  public class OperationBuilder<T>
    where T : class
  {
    private List<MethodInfo> methods;

    public OperationBuilder()
    {
      methods = new List<MethodInfo>();
    }

    public OperationBuilder<T> AddMethod( MethodInfo method )
    {
      methods.Add( method );
      return this;
    }

    public OperationBuilder<T> AddMethod( Expression<Func<T, Action>> expression )
      => AddMethod( expression.GetMethodInfo() );

    public OperationBuilder<T> AddMethod<T1>( Expression<Func<T, Action<T1>>> expression )
      => AddMethod( expression.GetMethodInfo() );

  }

  public class SomeClass
  {

    public void MethodA()
    {
    }

    public void MethodB( double value )
    {
    }

  }

}

public static class ExpressionExtensions
{
  public static MethodInfo GetMethodInfo<TClass>(
    this Expression<Func<TClass, Action>> expression )
    => GetMethodInfoInternal( expression );

  public static MethodInfo GetMethodInfo<TClass, T1>(
    this Expression<Func<TClass, Action<T1>>> expression )
    => GetMethodInfoInternal( expression );

  private static MethodInfo GetMethodInfoInternal( LambdaExpression expression )
  {
    if( !( expression.Body is UnaryExpression unary ) )
      throw new ArgumentException(
        "Expression is not unary.",
        nameof( expression ) );

    if( !( unary.Operand is MethodCallExpression methodCall ) )
      throw new ArgumentException(
        "Expression is not a method call.",
        nameof( expression ) );

    if( !( methodCall.Object is ConstantExpression constant )
      || !( constant.Value is MethodInfo methodInfo ) )
      throw new ArgumentException(
        "Expression does not contain a valid method reference.",
        nameof( expression ) );

    return methodInfo;
  }
}

通过指定 x.MethodB,您实际上并没有直接提供委托,而是 so-called method group. Method groups can be implicitly converted to a given compatible delegate type.

因此,编译器在尝试推断您的 AddMethod<T1> 方法的泛型类型参数 T1 时会面临以下情况。为了能够推断 T1,编译器需要知道手头的具体 Action<T1> 委托类型。但是编译器给出的只是一个方法组,而不是 Action<double> 委托。

为了能够(隐式)将方法组转换为委托,编译器需要知道方法组应转换为的兼容委托类型。但是编译器只有在可以推断 T1 时才能知道这个委托类型,这又需要它知道具体的 Action<T1> 类型。第二十二条军规

一个类似但更简单的问题,说明了相同的潜在问题:

static void SomeMethod<T1>(Action<T1> action) { }

var sc = new SomeClass();
SomeMethod(sc.MethodB); // compile error, type argument cannot be inferred

在这里,编译器也无法推断出 SomeMethod 的 类型参数 T1,因为它被赋予了一个方法组,而不是一个 Action<T1> 委托.而且它不能成功地将方法组转换为委托,因为要确定转换的实际具体 Action<T1> 类型,它需要有一个具体的 Action<T1> 类型来推断 T1 .

下面是一个小的(愚蠢且不切实际的)示例,表明问题不在于 T1 本身的推断,而是缺少给定的具体 Action<T1> 委托类型 T1 可以推断。在这里,只在 lambda 表达式中给出了一个 Action<double> 委托,这反过来将允许编译器推断 T1:

var sc = new SomeClass();
Action<double> d = sc.MethodB;
builder.AddMethod(x => d);    // Gives no error anymore

或者,不那么傻:

builder.AddMethod(x => (Action<double>) x.MethodB);    // Gives no error anymore