Почему вызов ISet <dynamic>.Contains () компилируется, но выдает исключение во время выполнения? - PullRequest
22 голосов
/ 12 сентября 2010

Пожалуйста, помогите мне объяснить следующее поведение:

dynamic d = 1;
ISet<dynamic> s = new HashSet<dynamic>();
s.Contains(d);

Код компилируется без ошибок / предупреждений, но в последней строке я получаю следующее исключение:

Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'System.Collections.Generic.ISet<object>' does not contain a definition for 'Contains'
   at CallSite.Target(Closure , CallSite , ISet`1 , Object )
   at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1)
   at FormulaToSimulation.Program.Main(String[] args) in 

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

(1) Если тип s равен HashSet<dynamic>, исключений не возникает.

(2) Если я использую неуниверсальный интерфейс с методом, принимающим динамический аргумент, исключение не возникает.

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

Это ошибка в системе компилятора / типов или законное поведение?

Ответы [ 5 ]

9 голосов
/ 12 сентября 2010

Полученные вами ответы не объясняют поведение, которое вы видите. DLR должен найти метод ICollection<object>.Contains(object) и вызвать его с целым числом в штучной упаковке в качестве параметра, даже если статический тип переменной равен ISet<dynamic> вместо ICollection<dynamic> (потому что первое происходит от последнего).

Поэтому я считаю, что это ошибка, и Я сообщил об этом в Microsoft Connect. Если выяснится, что такое поведение как-то желательно, они опубликуют комментарий эффект там.

3 голосов
/ 12 сентября 2010

Почему оно компилируется: все выражение оценивается как динамическое (наведите курсор мыши на него внутри IDE, чтобы подтвердить), что означает, что это проверка во время выполнения.

Почему это бомбы: мое (совершенно неверное, см. Ниже) предположение, что это потому, что вы не можете реализовать динамический интерфейс таким способом. Например, компилятор не позволяет вам создавать класс, который реализует ISet<dynamic>, IEnumerable<dynamic>, IList<dynamic> и т. Д. Вы получаете ошибку времени компиляции, в которой говорится, что «невозможно реализовать динамический интерфейс». Смотрите сообщение в блоге Криса Барроуза на эту тему.

http://blogs.msdn.com/b/cburrows/archive/2009/02/04/c-dynamic-part-vii.aspx

Однако, поскольку он в любом случае работает с DLR, вы можете сделать s полностью динамическим.

dynamic s = new HashSet<dynamic>;
s.Contains(d);

Компилирует и запускает.

Редактировать: вторая часть этого ответа совершенно неверна. Ну, это правильно, потому что вы не можете реализовать такой интерфейс, как ISet<dynamic>, но это не то, почему это взрывается.

См. Ответ Джулиана ниже. Вы можете получить следующий код для компиляции и run:

ICollection<dynamic> s = new HashSet<dynamic>();
s.Contains(d);
2 голосов
/ 12 сентября 2010

Метод Contains определен для ICollection<T>, а не ISet<T>.CLR не позволяет вызывать базовый метод интерфейса из производного интерфейса.Вы обычно не видите этого со статическим разрешением, потому что компилятор C # достаточно умен, чтобы отправлять вызов ICollection<T>.Contains, а не несуществующим ISet<T>.Contains.

Редактировать: DLR имитирует поведение CLR, поэтому вы получаете исключение.Ваш динамический вызов выполняется на ISet<T>, а не на HashSet<T>, DLR будет имитировать CLR: для интерфейса ищутся только методы интерфейсов, а не базовые интерфейсы (в отличие от классов, где присутствует такое поведение).

Более подробное объяснение см. В предыдущем моем ответе на аналогичный вопрос:

Странное поведение при использовании динамических типов в качестве параметров метода

0 голосов
/ 12 сентября 2010

Обратите внимание, что тип dynamic на самом деле не существует во время выполнения.Переменные этого типа фактически скомпилированы в переменные типа object, но компилятор превращает все вызовы методов (и свойства, и все остальное), которые включают такой объект (как объект this или как параметр) в вызовэто решается динамически во время выполнения (с использованием System.Runtime.CompilerServices.CallSiteBinder и связанной магии).

Итак, что происходит в вашем случае, так это то, что компилятор:

  • превращает ISet<dynamic> вISet<object>;

  • превращает HashSet<dynamic> в HashSet<object>, что становится фактическим типом времени выполнения экземпляра, который вы храните в s.

Теперь, если вы попытаетесь вызвать, скажем,

s.Contains(1);

, это на самом деле завершится успешно без динамического вызова: на самом деле он просто вызывает ISet<object>.Contains(object) в штучной упаковке целое число 1.

Но если вы попытаетесь вызвать

s.Contains(d);

, где d равно dynamic, то компилятор преобразует оператор в оператор, который во время выполнения определяет правильную перегрузку Contains ввызов на основе времени выполнения типа d.Возможно, теперь вы можете увидеть проблему:

  • Компилятор выдает код, который определенно ищет тип ISet<object>.

  • Этот код определяет, чтодинамическая переменная имеет тип int во время выполнения и пытается найти метод Contains(int).

  • ISet<object> не содержит метод Contains(int), следовательно, исключение.

0 голосов
/ 12 сентября 2010

Интерфейс ISet не имеет метода 'Contains', однако HashSet имеет?

EDIT Что я хотел сказать, так это то, что связыватель разрешает «Contains» при задании конкретного типа HashSet, но не находит унаследованный метод «Contains» в интерфейсе ...

...