2009-02-19 2 views
10

Я искал использование выражения lamba, чтобы события могли быть подключены строго типизированным образом, но со слушателем в середине, например. учитывая следующие классыСобытия в лямбда-выражениях - ошибка компилятора C#?

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: An expression tree may not contain an assignment operator

Теперь на первых порах это кажется разумным, особенно после того, как reading the explanation about why expression trees cannot contain assignments , Однако, несмотря на C# синтаксисе += не уступка, это вызов метода Producer::add_MyEvent, как мы можем видеть из КСС, который производится, если мы просто подключить событие нормально:

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:

Пожалуйста, обратите внимание, что вопрос "Является ли это поведение компилятора ошибка?". Извините, если я не понял, о чем я просил.

Edit 2

После прочтения ответа Inferis', где он говорит, что „в этой точке + = считается назначение“ это делает какой-то смысл, потому что в этот момент компилятор, возможно, не знайте, что это превратится в CIL.

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

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

Дает:

CS0571: 'Producer.MyEvent.add': cannot explicitly call operator or accessor

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

+1

+ = означает «вызывать средство доступа к событию для данного события». Я думаю, что это не ошибка как таковая, а немного раздражающее и, возможно, ненужное ограничение. –

ответ

5

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

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

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

+0

Джон - да, вот где я только что добрался (см. Мое редактирование № 2). Поэтому я думаю, что спецификация указывает на то, что + = с событиями означает «назначать событие в пока еще неопределенном порядке», а не «называть метод добавления для меня». В этом случае это было бы неприятно, но технически это не ошибка. –

+0

Я не думаю, что он даже «еще не определен» - он может идентифицировать метод и испускать дерево выражений, которое вызывает это. Это было бы разумным поведением, за исключением того, что он передавал бы оператор, который по-прежнему - строго говоря - оператор присваивания. –

+0

ОК, вы меня убедили; Я думаю, что это технически оператор присваивания, а это значит, что это не ошибка. Но это расстраивает, так как мы все знаем * это действительно метод вызова под ... ах хорошо: -S –

1

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

ли вы попробовать

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

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

+0

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

+0

В первую очередь я не понял суть всей конструкции слушателя:/ – Timbo

1

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

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

+0

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

+0

Достаточно честный. Мой пост теперь обновляется, чтобы обеспечить альтернативное решение. – Noldorin

+1

К сожалению, похоже, что нет никакого строго типизированного способа захвата p.MyEvent - попытка использовать его без + = результатов с ошибкой CS0070: событие «Producer.MyEvent» может появляться только с левой стороны of + = или - = (за исключением случаев, когда они используются внутри типа «Производитель») –

0

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

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

Я бы, тем не менее, рассмотрел возможность отправки этого файла на microsoft.

1

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

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

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

+0

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

+0

Но, я понимаю, что вы имеете в виду, я думаю, что ключевой частью этого ответа является «в этот момент» ... позвольте мне изменить вопрос. –

Смежные вопросы