създаване на генеричен делегат с помощта на Expression в C#

По-долу са дадени два метода, които създават делегат за задаване на поле в клас. Единият метод използва генерични лекарства, а другият не. И двата метода връщат делегат и работят добре. Но ако се опитам да използвам делегата, който е създаден в метода CreateDelegate, тогава негенеричният делегат 'del' работи добре. Мога да поставя точка на прекъсване на оператора за връщане и да извикам делегата, като напиша 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;
}

person umbersar    schedule 22.10.2011    source източник
comment
Пробвах и a(5.0) и b(5.0) и работят правилно. Имайте предвид, че този код е C# 4.0 (Expression.Assign е въведен там)   -  person xanatos    schedule 22.10.2011
comment
Можете ли да ни покажете пълен пример, който демонстрира проблема? Как се обаждате на делегатите?   -  person Ani    schedule 22.10.2011
comment
А... Добавих извикванията към a() и b() във вашия код, така че кодът може да се тества директно.   -  person xanatos    schedule 22.10.2011
comment
Е, това е, което казвам, че a() и b() ще работят добре. Но опитайте да извикате del() и genericDel() (като поставите точка на прекъсване преди методът CreateDelegate да се върне) и забелязвате, че genericDel() хвърля споменатото изключение, докато del() работи добре.   -  person umbersar    schedule 22.10.2011


Отговори (2)


Мисля, че разбрах проблема; имате проблеми с извикването на общ делегат от непосредствения прозорец, когато типът по време на компилиране на делегата е отворен общ тип. Ето едно по-просто повторение:

  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.

Това е спорно недостатък на непосредствения прозорец (той не изглежда да желае да използва допълнителни „познания по време на изпълнение“, за да ви помогне в този случай), но може да се твърди, че е разумно поведение, тъй като еквивалентно извикване, направено по време на компилиране (в изходния код), също би било незаконно. Това все пак изглежда като ъглов случай; непосредственият прозорец е напълно способен да присвоява общи променливи и да изпълнява друг код, който би бил незаконен по време на компилиране. Може би Рослин ще направи нещата много по-последователни.

Ако желаете, можете да заобиколите това така:

genericDel.DynamicInvoke(42D);

(or)

((Action<double>)(object)genericDel)(42D);
person Ani    schedule 22.10.2011
comment
Хей, Ани, да, това е проблемът. Бях го пробвал с извикването на DynamicInvoke (както споменахте тук), но се опитвах да разбера причината, поради която не работи в кода, както е написан. Както казвате тук, може да е грешка. И между другото, вашият втори съвет е различен начин за решаването му:((Action‹double›)(object)genericDel)(3434); Не знаех това. - person umbersar; 22.10.2011
comment
Има ли недостатък в използването на DynamicInvoke за решаване на проблема? - person Force444; 14.08.2014

Проблемът е, че се опитвате да извикате делегата в обхвата на метода, който го създава, преди 'T' да е известно. Той се опитва да преобразува тип стойност (цяло число) в общия тип „T“, което не е разрешено от компилатора. Ако се замислите, има смисъл. Трябва да можете да предавате само в T, стига да сте в обхвата на метода, който създава делегата, в противен случай той изобщо не би бил общ.

Трябва да изчакате методът да се върне, след което да използвате делегата. Не трябва да имате проблем с извикването на делегата след завършването му:

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

a(434); 
person Sean Thoman    schedule 22.10.2011
comment
И защо е така. Това документирано поведение ли е? Имам предвид, че когато контролата е вътре в метода, методът не знае ли какво е "T". Например, вече използвахме 'T', за да дефинираме numParam. - person umbersar; 22.10.2011
comment
Тъй като T е контейнер за всеки тип. Всъщност 'numParam' изобщо не е гарантирано, че е число, гарантирано е само, че е от тип T. Това е целият смисъл на общия аргумент. Наистина е погрешно да се обозначава тази променлива като „numParam“ - тя може (и трябва) да може да бъде от всякакъв тип, в противен случай не е родова. - person Sean Thoman; 22.10.2011
comment
Когато извикате CreateDelegate‹double›, в този случай, ако поставите точка на прекъсване на numParam, ще забележите, че 'T' е заменено с double. Ето типа за numParam, извлечен от QuickWatch:- Type = {Name = Double FullName = System.Double}. Така че 'T' е инстанциран в повикването. - person umbersar; 22.10.2011
comment
Грешката, която получавате, възниква по време на компилиране, за да ви попречи да направите нещо, което иначе би причинило проблеми по време на изпълнение. По време на компилиране типът 'T' не може и не трябва да бъде известен в рамките на обхвата на метода, в противен случай генериците не биха работили. Представете си, ако се опитате да извикате genericDel(434) вътре в метода, когато сте предали напълно различен общ параметър като CreateDelegate‹String› -- това би извело грешка по време на изпълнение, ако не сте получили проверката по време на компилиране предварително. Защото бихте се опитвали да преобразувате int в низ имплицитно. - person Sean Thoman; 22.10.2011
comment
Хей, приятел... не казвам, че правя обаждането по време на компилиране. Стартирайте програмата, поставете точка на прекъсване, преди методът createdelegate да се върне и изключете делегата в незабавен прозорец. - person umbersar; 22.10.2011
comment
А, сега разбирам какво имаш предвид. Това е малко по-различно, но все пак бих го приписал на факта, че „T“ трябва да остане общо, докато сте в обхвата на общия метод. DLR е разрешил типа, но CLR не е и все още очаква „T“ като аргумент. Забележете, че под капака .NET използва CompilerServices.Closure, за да направи тази работа. Това предполага, че затворените променливи са обвързани в обхвата на дървото на динамичния израз, а не непременно (по подразбиране) към външния метод, който го генерира. Както Ани вече спомена, може би Рослин ще се заеме с тези проблеми. - person Sean Thoman; 22.10.2011