Невозможно назначить делегата одного типа другому, даже если подпись совпадает

Мое нездоровое любопытство заставляет меня задаться вопросом, почему следующее терпит неудачу:

// declared somewhere
public delegate int BinaryOperation(int a, int b);

// ... in a method body
Func<int, int, int> addThem = (x, y) => x + y;

BinaryOperation b1 = addThem; // doesn't compile, and casting doesn't compile
BinaryOperation b2 = (x, y) => x + y; // compiles!

person Hobbes    schedule 17.12.2010    source источник


Ответы (2)


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

Из спецификации языка:

Типы делегатов в C# эквивалентны имени, но не структурно эквивалентны. В частности, два разных типа делегатов с одинаковыми списками параметров и типом возвращаемого значения считаются разными типами делегатов.

Попробуйте один из:

// C# 2, 3, 4 (C# 1 doesn't come into it because of generics)
BinaryOperation b1 = new BinaryOperation(addThem);

// C# 3, 4
BinaryOperation b1 = (x, y) => addThem(x, y);
var b1 = new BinaryOperation(addThem);
person Ani    schedule 17.12.2010
comment
Великолепно! Спасибо! (... Нужно подождать 11 минут, чтобы отметить свой ответ как ответ...) - person Hobbes; 17.12.2010
comment
Причина неструктурной типизации в том, что определение делегата может иметь семантику. Вы не должны иметь возможность назначать Func для SecureFunc, или PureFunc, или NoSideEffectsFunc, или чего-то еще. На практике никто не создает типы делегатов с семантикой; если бы нам пришлось делать это снова и снова, делегаты, вероятно, были бы более структурно типизированы. - person Eric Lippert; 17.12.2010
comment
@Eric Lippert: Это интересный момент. Как вы относитесь к тому, почему в BCL так много повторяющихся типов делегатов с одинаковой структурой, таких как Action, ThreadStart, MethodInvoker? - person Ani; 17.12.2010
comment
@Eric Lippert: Кроме того, зачем предоставлять неявное преобразование из группы методов, но не из структурно совместимого типа делегата? Назначение NoSideEffectsFunc группе методов, которая имеет побочные эффекты, имеет ту же проблему, не так ли? Это не то, чему должен мешать язык... Или я упускаю суть? - person Ani; 17.12.2010
comment
В этом конкретном примере можно также предположить, что компилятор может идентифицировать методы без побочных эффектов и иметь специальные знания о данном типе делегата, чтобы предупредить, если вы сделаете это неправильно. Но в целом в какой-то момент разработчик должен взять на себя ответственность за правильное использование инструментов. - person Eric Lippert; 17.12.2010
comment
@Eric Lippert: А, кажется, я понял. Теоретически компилятор может рассуждать о группе методов (скажем, она помечена атрибутом [Pure]), но не о произвольном экземпляре делегата (другого номинального типа), так как это не всегда возможно чтобы сказать, какой будет его цель во время выполнения? - person Ani; 18.12.2010
comment
@EricLippert: я понимаю, что, безусловно, должна быть возможность объявить тип делегата, который не будет поддерживать приведение типов к другому. Возникнет ли какая-то особая проблема с определением анонимного для каждой возможной подписи, аналогично тому, что делается с кортежами, и, возможно, позволит делегатам, помеченным определенным атрибутом, считаться взаимозаменяемыми с анонимным делегатом для их подписи? - person supercat; 17.04.2012
comment
@supercat: это был бы разумный подход к структурно типизированным делегатам; Я не вижу никакой технической проблемы как таковой. Вопрос, как всегда, в том, входит ли создание такой системы в список приоритетов команды CLR. - person Eric Lippert; 17.04.2012
comment
@Eric: я лично довольно часто определяю делегатов с семантикой; было бы очень обидно, если бы однажды делегаты внезапно стали типизированными. - person Vlad; 21.03.2013
comment
@Vlad: Ты первый, кто мне это сказал! Можете ли вы привести несколько примеров семантики делегатов, которые вы хотели бы закодировать? - person Eric Lippert; 21.03.2013

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

// declared somewhere
struct Foo {
    public int x;
    public int y;
}

struct Bar {
    public int x;
    public int y;
}

// ... in a method body
Foo item = new Foo { x = 1, y = 2 };

Bar b1 = item; // doesn't compile, and casting doesn't compile
Bar b2 = new Bar { x = 1, y = 2 }; // compiles!

В этом случае кажется более естественным, что приведение не работает, но на самом деле это та же причина.

person configurator    schedule 17.12.2010