在C#中使用Expression创建泛型委托

时间:2011-10-22 06:55:14

标签: c# generics delegates lambda expression

下面给出了两种创建委托来设置类中字段的方法。一种方法使用泛型而另一种方法不使用泛型。 这两个方法都返回一个委托,它们工作正常。但是,如果我尝试使用在CreateDelegate方法中创建的委托,那么非泛型委托'del'可以正常工作。我可以在return语句上放置一个断点,并通过写入del(222)来调用委托。但是如果我尝试通过编写genericDel(434)来调用泛型委托'genericDel',它会抛出异常:

  

委托'System.Action'有一些无效的参数

任何人都可以解释这个怪癖。

class test
{
    public double fld = 0;
}

public static void Main(string[] args)
{
    test tst = new test() { fld = 11 };

    Type myType = typeof(test);
    // Get the type and fields of FieldInfoClass.
    FieldInfo[] myFieldInfo = myType.GetFields(BindingFlags.Instance | BindingFlags.Public);
    var a = CreateDelegate<double>(myFieldInfo[0], tst);
    var b = CreateDelegate(myFieldInfo[0], tst);

    Console.WriteLine(tst.fld);

    b(5.0);
    Console.WriteLine(tst.fld);

    a(6.0);
    Console.WriteLine(tst.fld);
}

public static Action<T> CreateDelegate<T>(FieldInfo fieldInfo, object instance)
{
    ParameterExpression numParam = Expression.Parameter(typeof(T), "num");
    Expression a = Expression.Field(Expression.Constant(instance), fieldInfo);
    BinaryExpression assExp = Expression.Assign(a, numParam);

    Expression<Action<T>> expTree =
        Expression.Lambda<Action<T>>(assExp,
            new ParameterExpression[] { numParam });

    Action<T> genericDel = expTree.Compile();
    //try to invoke the delegate from immediate window by placing a breakpoint on the return below: genericDel(323)
    return genericDel;
}

public static Action<double> CreateDelegate(FieldInfo fieldInfo, object instance)
{
    ParameterExpression numParam = Expression.Parameter(typeof(double), "num");
    Expression a = Expression.Field(Expression.Constant(instance), fieldInfo);
    BinaryExpression assExp = Expression.Assign(a, numParam);

    Expression<Action<double>> expTree =
        Expression.Lambda<Action<double>>(assExp,
            new ParameterExpression[] { numParam });

    Action<double> del = expTree.Compile();
    //try to invoke the delegate from immediate window by placing a breakpoint on the return below: del(977)
    return del;
}

2 个答案:

答案 0 :(得分:6)

我想我理解这个问题;当委托的编译时类型是开放泛型类型时,您在从直接窗口调用泛型委托时遇到问题。 这是一个更简单的复制品:

  static void Main() { Test<double>(); }

  static void Test<T>()
  {
        Action<T> genericDel = delegate { };
       // Place break-point here.
  }

现在,如果我尝试在Test方法中执行此委托(通过放置断点并使用立即窗口),如下所示:

genericDel(42D);

我收到以下错误:

Delegate 'System.Action<T>' has some invalid arguments

请注意,此是您所声明的异常,而是compile-time error CS1594的“即时窗口版本”。

请注意,这样的调用在编译时会失败同样,因为没有从doubleT的隐式或显式转换。

这是故障即时窗口的缺点(在这种情况下似乎不愿意使用额外的'运行时知识'来帮助你),但有人可能会争辩这是合理的行为,因为在编译时(在源代码中)进行的等效调用也是非法的。这似乎是一个角落的情况;即时窗口完全能够分配通用变量并执行在编译时非法的其他代码。也许罗斯林会使事情更加一致。

如果您愿意,可以这样解决这个问题:

genericDel.DynamicInvoke(42D);

(或)

((Action<double>)(object)genericDel)(42D);

答案 1 :(得分:0)

问题是,在“T”已知之前,您正尝试在创建它的方法范围内调用委托。它试图将值类型(整数)转换为泛型类型“T”,这是编译器不允许的。如果你考虑一下,这是有道理的。只要您在创建委托的方法范围内,您应该只能传入T,否则它根本不是通用的。

您需要等待方法返回,然后使用委托。完成后调用委托应该没有问题:

var a = CreateDelegate<double>(myFieldInfo[0], tst);     
var b = CreateDelegate(myFieldInfo[0], tst); 

a(434);