Подтвердить что ты не робот

Почему вызов 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) Если я использую не общий интерфейс с методом, принимающим динамический аргумент, исключение не возникает.

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

Является ли это ошибкой в ​​системе компилятора/типа или законным поведением?

4b9b3361

Ответ 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, но не находит унаследованный метод "Содержит" в интерфейсе...