События в лямбда-выражениях - ошибка компилятора C #? - PullRequest
10 голосов
/ 19 февраля 2009

Я рассматривал использование выражения lamba, чтобы разрешить передачу событий строго типизированным образом, но с послушателем посередине, например, учитывая следующие классы

class Producer
{
    public event EventHandler MyEvent;
}

class Consumer
{
    public void MyHandler(object sender, EventArgs e) { /* ... */ }
}

class Listener
{
    public static void WireUp<TProducer, TConsumer>(
        Expression<Action<TProducer, TConsumer>> expr) { /* ... */ }
}

Событие будет записано как:

Listener.WireUp<Producer, Consumer>((p, c) => p.MyEvent += c.MyHandler);

Однако это дает ошибку компилятора:

CS0832: Дерево выражений может не содержать оператор присваивания

Сначала это кажется разумным, особенно после того, как прочитал объяснение того, почему деревья выражений не могут содержать присваивания . Однако, несмотря на синтаксис C #, += не является присваиванием, это вызов метода Producer::add_MyEvent, как мы видим из CIL, который создается, если мы просто подключаем событие нормально:

L_0001: newobj instance void LambdaEvents.Producer::.ctor()
L_0007: newobj instance void LambdaEvents.Consumer::.ctor()
L_000f: ldftn instance void LambdaEvents.Consumer::MyHandler(object, class [mscorlib]System.EventArgs)
L_0015: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
L_001a: callvirt instance void LambdaEvents.Producer::add_MyEvent(class [mscorlib]System.EventHandler)

Так что мне кажется, что это ошибка компилятора, так как он жалуется на то, что назначения не разрешены, но назначения не происходит, только вызов метода. Или я что-то упустил ...?

Edit:

Обратите внимание, что вопрос «Является ли это поведение ошибкой компилятора?». Извините, если мне неясно, о чем я спрашиваю.

Редактировать 2

После прочтения ответа Inferis, где он говорит, что «в этот момент + = считается присваиванием», это действительно имеет некоторый смысл, потому что в этот момент компилятор, вероятно, не знает, что он будет превращен в CIL .

Однако мне не разрешено писать явную форму вызова метода:

Listener.WireUp<Producer, Consumer>(
    (p, c) => p.add_MyEvent(new EventHandler(c.MyHandler)));

Дает:

CS0571: 'Producer.MyEvent.add': невозможно явно вызвать оператор или метод доступа

Итак, я предполагаю, что вопрос сводится к тому, что на самом деле означает += в контексте событий C #. Означает ли это «вызвать метод add для этого события» или это означает «добавить к этому событию еще не определенным образом». Если это первое, то мне кажется, что это ошибка компилятора, тогда как если это второе, то это несколько не интуитивно понятно, но, возможно, это не ошибка. Мысли?

Ответы [ 5 ]

5 голосов
/ 19 февраля 2009

В спецификации, раздел 7.16.3, операторы + = и - = называются «Назначение события», что, безусловно, делает его похожим на оператор присваивания. Тот факт, что он находится в разделе 7.16 («Операторы присваивания»), является довольно большой подсказкой :) С этой точки зрения ошибка компилятора имеет смысл.

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

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

1 голос
/ 19 февраля 2009

На самом деле, что касается компилятора в этот момент, то является назначением. Оператор + = перегружен, но компилятору это не важно. В конце концов, вы генерируете выражение с помощью лямбды (которая в какой-то момент будет скомпилирована с реальным кодом), а не с реальным кодом.

То, что делает компилятор, говорит: создайте выражение, в котором вы добавите c.MyHandler к текущему значению p.MyEvent и сохраните измененное значение обратно в p.MyEvent. И поэтому вы фактически выполняете задание, даже если в конце концов вы этого не делаете.

Есть ли причина, по которой вы хотите, чтобы метод WireUp принимал выражение, а не только действие?

1 голос
/ 19 февраля 2009

Почему вы хотите использовать класс Expression? Измените Expression<Action<TProducer, TConsumer>> в вашем коде на Action<TProducer, TConsumer>, и все должно работать так, как вы хотите. Здесь вы заставляете компилятор обрабатывать лямбда-выражение как дерево выражений, а не делегат, и дерево выражений действительно не может содержать такие присваивания (это рассматривается как присваивание, потому что я полагаю, что вы используете оператор + = ). Теперь лямбда-выражение можно преобразовать в любую форму (как указано в [MSDN] [1]). Простое использование делегата (вот и весь класс Action) делает такие «назначения» совершенно допустимыми. Возможно, я неправильно понял проблему здесь (возможно, есть особая причина, по которой вам нужно использовать дерево выражений?), Но, похоже, решение к счастью так просто!

Редактировать: Правильно, теперь я понимаю вашу проблему немного лучше из комментария. Есть ли какая-то причина, по которой вы не можете просто передать p.MyEvent и c.MyHandler в качестве аргументов метода WireUp и присоединить обработчик событий в методе WireUp (для меня это также кажется лучше с точки зрения дизайна) ... что не исключает необходимость в дереве выражений? Я думаю, что лучше в любом случае избегать деревьев выражений, так как они имеют тенденцию быть довольно медленными по сравнению с делегатами.

1 голос
/ 19 февраля 2009

+ = - это задание, независимо от того, что оно делает (например, добавляет событие). С точки зрения парсера, это все еще задание.

Вы пробовали

Listener.WireUp<Producer, Consumer>((p, c) => { p.MyEvent += c.MyHandler; } );
0 голосов
/ 19 февраля 2009

Мне кажется, проблема в том, что кроме объекта Expression<TDelegate> дерево выражений не статически типизировано с точки зрения компилятора. MethodCallExpression и друзья не выставляют статическую информацию о наборе.

Даже если компилятор знает все типы в выражении, эта информация выбрасывается при преобразовании лямбда-выражения в дерево выражений. (Посмотрите на код, сгенерированный для деревьев выражений)

Я, тем не менее, рассмотрю вопрос об отправке в Microsoft.

...