Почему компиляция в порядке, когда я использую метод Invoke, и не в порядке, когда я возвращаю Fun c напрямую? - PullRequest
28 голосов
/ 09 марта 2020

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

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> напрямую?

Ответы [ 3 ]

27 голосов
/ 09 марта 2020

Для понимания этого поведения необходимо знать две вещи.

  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);

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

Что касается типов go, то нет совместимого назначения между 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# для назначения методов или лямбда-выражений делегату, а не пытаться назначить несовместимый тип - следовательно, он компилируется.

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 голосов
/ 09 марта 2020

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

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

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

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

  • ...

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

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

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

2 голосов
/ 09 марта 2020

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

Ниже приводится определение Fun c делегат из MSDN Источники:

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

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

public delegate int test(int i);

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

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

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

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

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

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