Странность компилятора C # с конструкторами делегатов - PullRequest
9 голосов
/ 10 ноября 2011

Основываясь на следующем вопросе , я обнаружил странное поведение компилятора c #.

Допустимо следующее C #:

static void K() {}

static void Main()
{
  var k = new Action(new Action(new Action(K))));
}

Что я нахожустранно, что компилятор «деконструирует» переданный делегат.

Вывод ILSpy выглядит следующим образом:

new Action(new Action(new Action(null, ldftn(K)), ldftn(Invoke)).Invoke);

Как видно, он автоматически решает использовать метод Invokeделегировать.Но почему?

Как таковой, код неясен.Есть ли у нас трижды завернутый делегат (фактический) или внутренний делегат просто «скопирован» во внешние (моя первоначальная мысль).

Конечно, если намерение было похоже на то, как компилятор выдал код, нужнонаписал:

var k = new Action(new Action(new Action(K).Invoke).Invoke);

Похоже на декомпилированный код.

Кто-нибудь может объяснить причину этого «удивительного» преобразования?

Обновление:

Я могу думать только об одном возможном случае использования для этого;преобразование типа делегата.Например:

delegate void Baz();
delegate void Bar();
...
var k = new Baz(new Bar( new Action (K)));

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

Ответы [ 3 ]

5 голосов
/ 10 ноября 2011

В спецификации (раздел 7.6.10.5) говорится:

  • Новый экземпляр делегата инициализируется тем же списком вызовов, что и экземпляр делегата, заданный E.

Теперь предположим, что компилятор перевел это на что-то похожее на ваше предложение:

new Action( a.Target, a.Method)

Это только когда-либо создало бы делегат со списком вызовов метода single вызов.Для многоадресного делегата это будет нарушать спецификацию.

Пример кода:

using System;

class Program
{
    static void Main(string[] args)
    {
        Action first = () => Console.WriteLine("First");
        Action second = () => Console.WriteLine("Second");

        Action both = first + second;
        Action wrapped1 =
            (Action) Delegate.CreateDelegate(typeof(Action),
                                             both.Target, both.Method);
        Action wrapped2 = new Action(both);

        Console.WriteLine("Calling wrapped1:");
        wrapped1();

        Console.WriteLine("Calling wrapped2:");
        wrapped2();
    }
}

Вывод:

Calling wrapped1:
Second
Calling wrapped2:
First
Second

Как видите, Реальное поведение компилятора соответствует спецификации - ваше предлагаемое поведение не соответствует.

Это отчасти связано с несколько странной природой * иногда однократного, иногда многоадресного * Delegateконечно ...

3 голосов
/ 10 ноября 2011

Когда вы пытаетесь обработать делегат как метод, компилятор фактически использует метод Invoke() делегата. Так, например, две строки ниже компилируются в один и тот же IL (обе они вызывают Invoke()):

k();
k.Invoke();

Я предполагаю, что странность, которую вы видите, является следствием этого. Конструктор делегата ожидает метод (точнее, группу методов), но вместо этого он получает делегат. Поэтому он рассматривает его как метод и использует метод Invoke().

Что касается значения, то делегат вызывает делегат, который вызывает фактический метод. Вы можете проверить это самостоятельно, обратившись к свойствам делегата Method и Target. В случае самого внешнего делегата Method - это Action.Invoke, а Target - внутренний делегат.

2 голосов
/ 10 ноября 2011
  • делегат класса
  • У делегата действия есть конструктор, подобный этому

    public extern Action (объект @object, метод IntPtr);

  • Так как K является статическим методом, нет необходимости передавать объект внутреннему большинству экземпляра действия в качестве первого аргумента и, следовательно, он принимает значение null

  • Поскольку второй аргумент является указателем на функцию, поэтому он передает указатель на метод K, используя функцию ldftn
  • для остальных экземпляров Action объект, который передается, является внутренним Action, а вторым параметром является метод Invoke , поскольку при вызове делегата вы фактически вызываете метод Invoke

Краткое описание

var action = new Action(K) => Action action = new Action(null, ldftn(K))
new Action(action) => new Action(action, ldftn(Action.Invoke))

Надеюсь, это объясняет, что происходит?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...