Передача делегата объекту MethodInfo с использованием Visual C # [duplicate]

Нет, это невозможно. Почему бы просто не сделать это:

#define replace_to replacement here
37
задан AndreyAkinshin 15 December 2009 в 12:22
поделиться

8 ответов

Вы можете взломать бросок, используя трюк, в котором вы используете эквивалент c # для объединения c ++. Сложная часть - это структура с двумя членами, которые имеют [FieldOffset (0)]:

[TestFixture]
public class Demo
{
    public void print(int i)
    {
        Console.WriteLine("Int: "+i);
    }

    private delegate void mydelegate(int i);

    [StructLayout(LayoutKind.Explicit)]
    struct funky
    {
        [FieldOffset(0)]
        public mydelegate a;
        [FieldOffset(0)]
        public System.Action<int> b;
    }

    [Test]
    public void delegatetest()
    {
        System.Action<int> f = print;
        funky myfunky;
        myfunky.a = null;
        myfunky.b = f;

        mydelegate a = myfunky.a;

        a(5);
    }
}
44
ответ дан Glenn Slayden 19 August 2018 в 01:03
поделиться
  • 1
    Может быть, подчеркнуть тот факт, что пользовательский материал, который вы написали, обычно делается с помощью магии компилятора. – Dykam 15 December 2009 в 12:44
  • 2
    +1 для приятного объяснения. Существует более простой способ, чем Func & lt; int, int & gt; c = x = & gt; a (x); , хотя - см. мой ответ. – Winston Smith 15 December 2009 в 13:35
  • 3
    Предполагаю, что в последнем бите public Foo должно быть public MyLambda? – Chris 23 June 2014 в 13:29
  • 4
    Впервые я слышал об этом удивительно опасном хаке. Благодаря! – Ashe 21 July 2014 в 07:36
SomeDelegate a = Inc;
Func<int, int> b = Inc;

не подходит для

SomeDelegate a = new SomeDelegate(Inc); // no cast here
Func<int, int> b = new Func<int, int>(Inc);

Вы не можете использовать экземпляр SomeDelegate для функции Func & lt; int, int & gt; по той же причине вы не можете передать строку в словарь & lt; int, int & gt; - они разные.

Это работает:

Func<int, int> c = x => a(x);

, который является синтаксическим сахаром для

class MyLambda
{
   SomeDelegate a;
   public MyLambda(SomeDelegate a) { this.a = a; }
   public int Invoke(int x) { return this.a(x); }
}

Func<int, int> c = new Func<int, int>(new MyLambda(a).Invoke);
44
ответ дан Glenn Slayden 19 August 2018 в 01:03
поделиться
  • 1
    Может быть, подчеркнуть тот факт, что пользовательский материал, который вы написали, обычно делается с помощью магии компилятора. – Dykam 15 December 2009 в 12:44
  • 2
    +1 для приятного объяснения. Существует более простой способ, чем Func & lt; int, int & gt; c = x = & gt; a (x); , хотя - см. мой ответ. – Winston Smith 15 December 2009 в 13:35
  • 3
    Предполагаю, что в последнем бите public Foo должно быть public MyLambda? – Chris 23 June 2014 в 13:29

Попробуйте следующее:

Func<int, int> c = (Func<int, int>)Delegate.CreateDelegate(typeof(Func<int, int>), 
                                                           b.Target,
                                                           b.Method);
24
ответ дан Diego Mijelshon 19 August 2018 в 01:03
поделиться
  • 1
    Взгляните на мой ответ, чтобы получить более простой способ достичь этого. – Winston Smith 15 December 2009 в 13:44
  • 2
    Уродливый, но полезный, если вы не можете позволить себе ссылку на Func<int, int> c = b.Invoke;. Но есть одна вещь, которую нужно помнить: в .NET все типы делегатов являются «многоадресными». делегаты, и приведенный выше код будет использовать только метод last в списке вызовов. Если вы не можете гарантировать, что в списке вызовов есть только один член, вам придется перебирать его, я думаю. – Jeppe Stig Nielsen 6 November 2012 в 00:38
  • 3
    @JeppeStigNielsen Я не думаю, что многоадресная рассылка Func имеет смысл. Какую возвращаемую стоимость вы бы использовали? – Diego Mijelshon 6 November 2012 в 18:32
  • 4
    Вы правы, он используется в основном с типом возврата void, например EventHandler делегатами со многими подписчиками. Но попробуйте это: Func<int> a = () => 10; Func<int> b = () => 20; Func<int> c = () => 30; var multi = a + b + c; int answer = multi(); Это законно в языке и структуре, поэтому вы должны убедиться, что никто не использовал его. – Jeppe Stig Nielsen 6 November 2012 в 18:42
  • 5
    Но спецификация языка C # говорит: Если вызов делегата включает выходные параметры или возвращаемое значение, их окончательное значение будет получено из вызова последнего делегата в списке. Итак, все методы в вызове список, и каждый из них может иметь «побочные эффекты», но возвращаемое значение всего вызова делегата является только возвращаемым значением метода last в списке вызовов. – Jeppe Stig Nielsen 6 November 2012 в 22:58

Проблема в том, что:

SomeDelegate a = Inc;

На самом деле это не актерский состав. Это короткая форма:

SomeDelegate a = new SomeDelegate(Inc);

Поэтому нет отличных результатов. Простым решением вашей проблемы может быть это (в C # 3.0)

Func<int,int> f = i=>a(i);
7
ответ дан Gamlor 19 August 2018 в 01:03
поделиться
  • 1
    Проблема в том, что вы фактически обертываете делегата новым, который использует анонимный метод, который имеет стоимость. – Diego Mijelshon 15 December 2009 в 13:00
  • 2
    Да, ты прав. Элегантный код и производительность. В зависимости от того, что вам нужно, что вы выбираете. – Gamlor 15 December 2009 в 13:13

Это работает (в C # 4.0 по крайней мере - не пробовали в более ранних версиях):

SomeDelegate a = Inc;
Func<int, int> c = new Func<int, int>(a);

Если вы посмотрите на IL, это скомпилируется в тот же код, что и ответ Winston. Вот IL для второй строки того, что я только что написал:

ldloc.0
ldftn      instance int32 ConsoleApplication1.Program/SomeDelegate::Invoke(int32)
newobj     instance void class [mscorlib]System.Func`2<int32,int32>::.ctor(object, native int)

И это также то, что вы видите, если назначить a.Invoke на c.

Кстати, хотя решение Диего более эффективно, поскольку полученный делегат ссылается непосредственно на базовый метод, а не на другой делегат, он не обрабатывает делегатов многоадресной рассылки правильно. Решение Winston делает это, потому что оно просто полностью подчиняется другому делегату. Если вам нужно прямое решение, которое также обрабатывает делегатов с несколькими целями, вам нужно что-то более сложное:

public static TResult DuplicateDelegateAs<TResult>(MulticastDelegate source)
{
    Delegate result = null;
    foreach (Delegate sourceItem in source.GetInvocationList())
    {
        var copy = Delegate.CreateDelegate(
            typeof(TResult), sourceItem.Target, sourceItem.Method);
        result = Delegate.Combine(result, copy);
    }

    return (TResult) (object) result;
}

Это делает правильную вещь для делегатов с одной целью, кстати, - это закончится создавая только один делегат целевого типа, который ссылается непосредственно на любой метод (и, где это применимо, объект), на который ссылается входной делегат.

7
ответ дан Ian Griffiths 19 August 2018 в 01:03
поделиться
  • 1
    Интересно, почему Method и Target делегата с несколькими вызовами указывают на одну из своих целей. Я бы подумал, что было бы разумнее, чтобы точка Target делегата была самой, и Method указывают на свой метод Invoke [который будет проверять, вызывал ли он себя, и если да, используйте многоадресную рассылку список] или "вызывать многоадресную рассылку" метод. Это позволило бы избежать риска случайного превращения делегата с несколькими вызовами в одноразовый вызов. – supercat 22 January 2013 в 23:31
  • 2
    Вся ситуация вокруг MulticastDelegate является беспорядком, потому что Microsoft изменила свое мнение о том, как справиться с этим довольно поздно в тот же день. В первом публичном просмотре .NET некоторые делегаты были многоадресными, а некоторые - нет. В конце концов они решили отказаться от этого различия, но на самом деле не успели очистить вещи, что оставило несколько аномалий в иерархии типов делегатов. – Ian Griffiths 20 February 2013 в 11:03

Это та же проблема:

public delegate int SomeDelegate1(int p);
public delegate int SomeDelegate2(int p);
...
  SomeDelegate1 a = new SomeDelegate1(Inc);
  SomeDelegate2 b = (SomeDelegate2)a;  // CS0030

, которая является такой же проблемой, как:

public class A { int prop { get; set; } }
public class B { int prop { get; set; } }
...
  A obja = new A();
  B objb = (B)obja;  // CS0029

Объекты не могут быть выбраны из одного типа в несвязанный другой тип, даже если типы в противном случае полностью совместимы. Из-за отсутствия лучшего термина: объект имеет идентификатор типа, который он переносит во время выполнения. Эта идентичность не может быть изменена после создания объекта. Видимым проявлением этого тождества является Object.GetType ().

4
ответ дан Rob 19 August 2018 в 01:03
поделиться

Мне нравятся примеры. Вот мой пример кода:

class Program
{
    class A
    {
        public A(D d) { d.Invoke("I'm A!"); }
        public delegate string D(string s);
    }

    class B
    {
        public delegate string D(string s);
    }
    static void Main(string[] args)
    {
        //1. Func to delegates 

        string F(dynamic s) { Console.WriteLine(s); return s; }
        Func<string, string> f = F;
        //new A(f);//Error CS1503  Argument 1: cannot convert from 'System.Func<string, string>' to 'ConsoleApp3.Program.A.D'  
        new A(new A.D(f));//I'm A!
        new A(x=>f(x));//I'm A!

        Func<string, string> f2 = s => { Console.WriteLine(s); return s; };
        //new A(f2);//Same as A(f)
        new A(new A.D(f2));//I'm A!
        new A(x => f2(x));//I'm A!

        //You can even convert between delegate types
        new A(new A.D(new B.D(f)));//I'm A!



        //2. delegate to F

        A.D d = s => { Console.WriteLine(s); return s; };

        Func<string, string> f3 = d.Invoke;
        f3("I'm f3!");//I'm f3!
        Func<string, string> f4 = new Func<string, string>(d);
        f4("I'm f4!");//I'm f4!


        Console.ReadLine();
    }
}

Выход:

0
ответ дан tymtam 19 August 2018 в 01:03
поделиться

Существует гораздо более простой способ сделать это, о чем пропустили все остальные ответы:

Func<int, int> c = a.Invoke; 

Подробнее см. в в этом сообщении в блоге .

53
ответ дан Winston Smith 19 August 2018 в 01:03
поделиться
  • 1
    Ницца. Это похоже на решение Gamlor, но без анонимного метода. Тем не менее, это оболочка оригинального делегата, в отличие от моего предлагаемого решения. – Diego Mijelshon 15 December 2009 в 16:43
  • 2
    Если кто-то не знает, что означает Диего, взгляните на свойства Target и Method оригинального делегата 'a' и делегата 'c'. С механизмом Диего «c» указывает прямо на оригинальный метод, как и на «a». С методом Winston это не так - он указывает на делегата, который, в свою очередь, указывает на оригинальный метод, поэтому вы получаете ненужный дополнительный уровень косвенности. – Ian Griffiths 15 February 2012 в 11:27
  • 3
    Как упоминал Диего и Ян, это решение обертывает оригинал оригинального делегата. Поэтому, если вы используете это решение с событиями, вы не сможете отказаться от подписки. С решением Диего вы можете. – Verax 1 May 2012 в 03:18
  • 4
    Чрезвычайно изящный, спасибо! – Vlad 18 May 2012 в 14:12
  • 5
    @Verax @IanGriffiths: Я сделал быстрый эксперимент (net45) и a.Invoke, кажется, отписался нормально? gist.github.com/dtchepak/7799703 – David Tchepak 5 December 2013 в 05:33
Другие вопросы по тегам:

Похожие вопросы: