Почему компиляция в порядке, когда я использую метод Invoke, и не в порядке, когда я возвращаю Func <int, int> напрямую?

28

Я не понимаю этот случай:

public delegate int test(int i);

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // <- code doesn't compile
}

Почему компиляция в порядке, когда я использую Invokeметод, и не в порядке, когда я возвращаюсь csharp Func<int,int>напрямую?

Евгений Терехин
источник
У вас есть делегат, что означает, что вы получаете какое-то событие. Invoke предотвращает межпотоковое исключение и позволяет нескольким процессам получать доступ к объекту.
jdweng
Обратите внимание, что вы увидите эту проблему, даже если вы используете двух одинаково выглядящих делегатов, таких как delegate void test1(int i);иdelegate void test2(int i);
Мэтью Уотсон,

Ответы:

27

Есть две вещи, которые вы должны знать, чтобы понять это поведение.

  1. Все делегаты являются производными System.Delegate, но разные делегаты имеют разные типы и поэтому не могут быть назначены друг другу.
  2. Язык C # обеспечивает специальную обработку для назначения метода или лямбды делегату .

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

Например, учитывая:

delegate void test1(int i);
delegate void test2(int i);

Затем:

test1 a = Console.WriteLine; // Using special delegate initialisation handling.
test2 b = a;                 // Using normal assignment, therefore does not compile.

Первая строка выше компилирует OK, потому что она использует специальную обработку для назначения лямбда-функции или метода делегату.

Фактически, эта строка эффективно переписывается компилятором следующим образом:

test1 a = new test1(Console.WriteLine);

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

Что касается типов, не существует совместимого назначения между test1и test2потому, что они разных типов.

Если это помогает думать об этом, рассмотрите эту иерархию классов:

class Base
{
}

class Test1 : Base
{
}

class Test2 : Base
{
}

Следующий код не будет компилироваться, хотя Test1и Test2вытекает из того же базового класса:

Test1 test1 = new Test1();
Test2 test2 = test1; // Compile error.

Это объясняет, почему вы не можете назначить один тип делегата другому. Это просто нормальный язык C #.

Однако важно понять, почему вы можете назначить метод или лямбду совместимому делегату. Как отмечалось выше, это является частью поддержки языка C # для делегатов.

Итак, наконец, чтобы ответить на ваш вопрос:

При использовании Invoke()вы назначаете делегату вызов METHOD, используя специальную обработку языка C # для назначения методов или лямбда-выражений делегату, а не пытаетесь назначить несовместимый тип - следовательно, он компилируется OK.

Чтобы быть полностью понятным, код, который компилируется в вашем OP:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

Фактически конвертируется концептуально в нечто вроде:

public test Success()
{
    Func<int, int> f = x => x;
    return new test(f.Invoke);
}

Принимая во внимание, что код ошибки пытается назначить между двумя несовместимыми типами:

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // Attempting to assign one delegate type to another: Fails
}
Мэтью Уотсон
источник
6

Во втором случае fимеет тип Func<int, int>, но говорят, что метод возвращает a test. Это несвязанные (делегированные) типы, которые необратимы друг для друга, поэтому возникает ошибка компилятора. Вы можете перейти к этому разделу спецификации языка и выполнить поиск «делегат». Вы не найдете упоминаний о преобразованиях между делегатами с одинаковыми подписями.

В первом случае, однако, f.Invokeэто выражение группы методов , которое на самом деле не имеет типа. Компилятор C # преобразует выражения группы методов в конкретные типы делегатов в соответствии с контекстом посредством преобразования группы методов .

(Цитирую 5-ую пулю здесь , выделение мое)

Выражение классифицируется как одно из следующих:

  • ...

  • Группа методов, которая представляет собой набор перегруженных методов, являющихся результатом поиска члена. [...] Группа методов допускается в выражениях invocation_expression, Delegate_creation_expression и в качестве левой части isоператора и может быть неявно преобразована в совместимый тип делегата.

В этом случае он преобразуется в testтип делегата.

Другими словами, return fне работает, потому что fуже имеет тип, но f.Invokeеще не имеет тип.

уборщик
источник
2

Проблема здесь: Тип совместимости:

Ниже приведено определение делегата Func из MSDN Sources:

public delegate TResult Func<in T, out TResult>(T arg);

Если вы видите, что нет прямой связи между Func, упомянутым выше, и вашим определенным делегатом:

public delegate int test(int i);

Почему первый фрагмент компилируется:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
 }

Делегаты сравниваются с использованием сигнатуры, то есть входных параметров и результата вывода, в конечном итоге делегат является указателем на функцию, а две функции можно сравнивать только через подпись. Во время выполнения метод, вызываемый через Func, присваивается Testделегату, поскольку Signature одинакова и работает без проблем. Это назначение указателя функции, где Testделегат теперь будет вызывать метод, указанный делегатом Func.

Почему 2-й фрагмент не компилируется

Между Func и тестовым делегатом нет совместимости типов / назначений, Func не может заполнить как часть правил системы типов. Даже тогда, когда его результат может быть назначен и заполнен, test delegateкак в первом случае.

Мринал Камбой
источник