Почему вызов ISet <dynamic>.Contains() компилирует, но генерирует исключение во время выполнения?
Пожалуйста, помогите мне объяснить следующее поведение:
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) Если я использую не общий интерфейс с методом, принимающим динамический аргумент, исключение не возникает.
Таким образом, похоже, что эта проблема связана, в частности, с общими интерфейсами, но я не мог выяснить, что именно вызывает проблему.
Является ли это ошибкой в системе компилятора/типа или законным поведением?
Ответы
Ответ 1
Ответы, которые вы получили до сих пор, не объясняют поведение, которое вы видите. DLR должен найти метод ICollection<object>.Contains(object)
и называть его с помощью целочисленного числа в виде пакета, даже если статический тип переменной ISet<dynamic>
вместо ICollection<dynamic>
(потому что первое происходит от последнего).
Поэтому я считаю, что это ошибка, и Я сообщил об этом Microsoft Connect. Если это окажется что поведение как-то желательно, они опубликуют там комментарий.
Ответ 2
Почему он компилируется: все выражение оценивается как динамическое (наведите указатель мыши на него внутри своей 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>
, но это не то, почему это взрывается.
См. ответ Джулиана ниже. Вы можете получить следующий код для компиляции и запуска:
ICollection<dynamic> s = new HashSet<dynamic>();
s.Contains(d);
Ответ 3
Метод Contains
определяется на ICollection<T>
, а не ISet<T>
. CLR не позволяет вызывать базовый метод интерфейса из производного интерфейса. Обычно вы не видите это со статическим разрешением, потому что компилятор С# достаточно умен, чтобы вызывать вызов ICollection<T>.Contains
, а не несуществующий ISet<T>.Contains
.
Изменить: DLR имитирует поведение CLR, поэтому вы получаете исключение. Ваш динамический вызов выполняется на ISet<T>
, а не HashSet<T>
, DLR будет имитировать CLR: для интерфейса ищутся только методы интерфейсов, а не базовые интерфейсы (в отличие от классов, где это поведение присутствует).
Подробное объяснение см. в предыдущем ответе моего вопроса на похожий вопрос:
Странное поведение при использовании динамических типов в качестве параметров метода
Ответ 4
Обратите внимание, что тип 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)
, поэтому исключение.
Ответ 5
Интерфейс ISet не имеет метода "Содержит" , однако HashSet?
ИЗМЕНИТЬ
Я хотел сказать, что связующее вещество разрешает "Содержит" при задании типа конкретизации HashSet, но не находит унаследованный метод "Содержит" в интерфейсе...