Разница между неявным и явным созданием делегатов (с дженериками и без них)

Посмотрите на четыре строки в методе Go() ниже:

delegate void Action<T>(T arg);
delegate void Action();

void DoSomething<T>(Action<T> action)
{
    //...
}

void DoSomething(Action action)
{
    //...
}

void MyAction<T>(T arg)
{
    //...
}

void MyAction()
{
    //...
}

void Go<T>()
{
    DoSomething<T>(MyAction<T>); // throws compiler error - why?
    DoSomething(new Action<T>(MyAction<T>)); // no problems here
    DoSomething(MyAction); // what's the difference between this...
    DoSomething(new Action(MyAction)); // ... and this?
}

Обратите внимание, что ошибка компилятора, сгенерированная первым вызовом: Аргументы типа для метода 'Action(T)' не могут быть выведены из использования. Попробуйте явно указать аргументы типа.


person Nathan Ridley    schedule 14.05.2009    source источник
comment
Я не могу понять, как это будет компилироваться (изолировано): void DoSomething(Action‹T› action) { //... } Поскольку T не объявлен... Все это внутри универсального класса?   -  person mmx    schedule 14.05.2009
comment
Да, я сейчас изменяю свой пример. Надо было прочитать Go‹T›(){...   -  person Nathan Ridley    schedule 14.05.2009
comment
Это я, но когда я компилирую код, который у вас есть, он компилируется без ошибок. Предполагая, что я размещаю код в неуниверсальном классе. Я использую С# 3.5 SP1   -  person JoshBerke    schedule 14.05.2009
comment
Мой код фактически появляется в наборе общих методов расширения.   -  person Nathan Ridley    schedule 14.05.2009
comment
Я не могу воспроизвести проблему. Этот код отлично компилируется в C# 2, 3 и 4, когда вы заключаете его в класс. Если вам нужен анализ проблемы, опубликуйте код, который действительно демонстрирует проблему.   -  person Eric Lippert    schedule 14.05.2009


Ответы (3)


Нет никакой разницы между MyAction и new Action(MyAction) (когда они оба допустимы), за исключением того, что первый не будет работать в C# 1. Это implicit method group conversion. Бывают случаи, когда это неприменимо, особенно когда компилятор не может определить, какой делегат вам нужен, например.

Delegate foo = new Action(MyAction); // Fine
Delegate bar = MyAction; // Nope, can't tell target type

Это играет роль в вашем вопросе, потому что оба задействованных метода перегружены. В основном это приводит к головным болям.

Что касается дженериков - это интересно. Группы методов не очень любят вывод типов в C# 3 — я не уверен, будет ли это улучшено в C# 4 или нет. Если вы вызываете общий метод и указываете аргумент типа, вывод типа работает довольно хорошо, но если вы попытаетесь сделать это наоборот, это не удастся:

using System;

class Test
{
    static void Main()
    {
        // Valid - it infers Foo<int>
        DoSomething<int>(Foo);
        // Valid - both are specified
        DoSomething<int>(Foo<int>);
        // Invalid - type inference fails
        DoSomething(Foo<int>);
        // Invalid - mismatched types, basically
        DoSomething<int>(Foo<string>);
    }

    static void Foo<T>(T input)
    {
    }

    static void DoSomething<T>(Action<T> action)
    {
        Console.WriteLine(typeof(T));
    }
}

Вывод типов в C# 3 очень сложен и хорошо работает в большинстве случаев (в частности, он отлично подходит для LINQ), но не работает в некоторых других случаях. В идеале в будущих версиях будет проще понять и мощнее... посмотрим!

person Jon Skeet    schedule 14.05.2009
comment
Ваше здоровье! Я принял другой ответ, но ваш тоже полезен. +1 - person Nathan Ridley; 14.05.2009

Создание необобщенного неявного делегата — это просто синтаксический сахар, поэтому компилятор генерирует точно такой же код для

DoSomething(MyAction);

и

DoSomething(new Action(MyAction));

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

Для универсального делегата необходимо указать тип делегата из-за ковариантности и контравариантности (см. http://msdn.microsoft.com/en-us/library/ms173174(VS.80).aspx для подробностей) - T в действии может быть супертипом для T в метод, и он по-прежнему будет принят в качестве метода делегата. Итак, вам нужно указать T в делегате явно, так как компилятор сам не может понять это.

person thecoop    schedule 14.05.2009
comment
Ура, теперь я понял. Ссылка на статью о ковариантности и контравариантности для тех, кто не понимает: en.wikipedia.org/wiki / - person Nathan Ridley; 14.05.2009
comment
Я бы сказал, что это больше связано с ограничениями вывода типов, чем с ко/контравариантностью, лично. Если вы прочтете раздел спецификации, посвященный выводу типов (сейчас я не могу вспомнить его навскидку), вы увидите, что у групп методов практически нет шансов. - person Jon Skeet; 14.05.2009

Просто примечание. По какой-то причине это работает в VB.

Кажется, что реализация препроцессора в C# и VB различается, когда я прихожу к преобразованию Methodgroup/adderessof в делегат.

person PEtter    schedule 15.02.2017