создание общего делегата с использованием Expression в C # - PullRequest
4 голосов
/ 22 октября 2011

Ниже приведены два метода, которые создают делегата для установки поля в классе.Один метод использует дженерики, а другой нет.Оба метода возвращают делегата и работают нормально.Но если я попытаюсь использовать делегат, который был создан внутри метода CreateDelegate, то неуниверсальный делегат 'del' будет работать нормально.Я могу поставить точку останова в операторе return и вызвать делегат, написав del (222).Но если я попытаюсь вызвать универсальный делегат 'genericDel', написав genericDel (434), он выдаст исключение:

Делегат 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 ]

6 голосов
/ 22 октября 2011

Мне кажется, я понял проблему; у вас возникают проблемы при вызове универсального делегата из непосредственного окна, когда тип времени компиляции делегата является открытым универсальным типом. Вот более простое воспроизведение:

  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

Обратите внимание, что это не исключение, как вы заявили, а скорее "немедленная версия окна" ошибка времени компиляции CS1594 .

Обратите внимание, что такой вызов потерпел бы неудачу в равной степени во время компиляции, потому что нет явного или явного преобразования из double в T.

Это 1028 * спорный * недостаток непосредственного окна (это не кажется, готовы использовать дополнительное «время выполнения знания», чтобы помочь вам в этом случае), но можно утверждать, что это разумное поведение, поскольку эквивалентный вызов, выполняемый во время компиляции (в исходном коде), также был бы недопустимым. Это похоже на угловой случай; непосредственное окно вполне может назначать универсальные переменные и выполнять другой код, который был бы недопустим во время компиляции. Возможно, Рослин сделает вещи намного более последовательными.

Если вы хотите, вы можете обойти это так:

genericDel.DynamicInvoke(42D);

(или)

((Action<double>)(object)genericDel)(42D);
0 голосов
/ 22 октября 2011

Проблема заключается в том, что вы пытаетесь вызвать делегата в рамках метода, который его создает, до того, как станет известно «T». Он пытается преобразовать тип значения (целое число) в универсальный тип 'T', что не разрешено компилятором. Если вы думаете об этом, это имеет смысл. Вы должны иметь возможность передавать T только в том случае, если находитесь в области действия метода, создающего делегат, иначе он вообще не будет универсальным.

Вам нужно дождаться возвращения метода, а затем использовать делегата. У вас не должно возникнуть проблем с вызовом делегата после его завершения:

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

a(434); 
...