Получилось немного затянуто, так что вот короткая версия:
Почему это вызывает исключение TypeLoadException во время выполнения? (И должен ли компилятор запрещать мне это делать?)
interface I
{
void Foo<T>();
}
class C<T1>
{
public void Foo<T2>() where T2 : T1 { }
}
class D : C<System.Object>, I { }
Исключение возникает, если вы пытаетесь создать экземпляр D.
Более длинная, более ознакомительная версия:
Учитывать:
interface I
{
void Foo<T>();
}
class C<T1>
{
public void Foo<T2>() where T2 : T1 { }
}
class some_other_class { }
class D : C<some_other_class>, I { } // compiler error CS0425
Это недопустимо, потому что ограничения типа для C.Foo()
не совпадают с ограничениями для I.Foo()
. Он генерирует ошибку компилятора CS0425.
Но я подумал, что смогу нарушить правило:
class D : C<System.Object>, I { } // yep, it compiles
Используя Object
в качестве ограничения для T2, я отрицаю это ограничение. Я могу спокойно передать любой тип в D.Foo<T>()
, потому что все происходит от Object
.
Тем не менее, я все еще ожидал получить ошибку компилятора. С точки зрения языка C#, это нарушает правило, согласно которому "ограничения для C.Foo() должны совпадать с ограничениями для I.Foo()", и я думал, что компилятор будет придерживаться этого правила. правила. Но компилируется. Кажется, компилятор видит, что я делаю, понимает, что это безопасно, и закрывает глаза.
Я думал, что мне это сошло с рук, но среда выполнения говорит не так быстро. Если я попытаюсь создать экземпляр D
, я получу исключение TypeLoadException: «Метод 'C`1.Foo' для типа 'D' попытался неявно реализовать метод интерфейса с более слабыми ограничениями параметров типа».
Но разве эта ошибка технически неверна? Разве использование Object
вместо C<T1>
не отменяет ограничение на C.Foo()
, тем самым делая его эквивалентным - НЕ сильнее, чем - I.Foo()
? Компилятор, похоже, согласен, но среда выполнения — нет.
Чтобы доказать свою точку зрения, я упростил ее, убрав D
из уравнения:
interface I<T1>
{
void Foo<T2>() where T2 : T1;
}
class some_other_class { }
class C : I<some_other_class> // compiler error CS0425
{
public void Foo<T>() { }
}
Но:
class C : I<Object> // compiles
{
public void Foo<T>() { }
}
Это компилируется и отлично работает для любого типа, переданного в Foo<T>()
.
Почему? Есть ли ошибка в среде выполнения или (что более вероятно) причина этого исключения, которую я не вижу, и в этом случае компилятор не должен был меня остановить?
Интересно, что если сценарий изменить на противоположный, переместив ограничение из класса в интерфейс...
interface I<T1>
{
void Foo<T2>() where T2 : T1;
}
class C
{
public void Foo<T>() { }
}
class some_other_class { }
class D : C, I<some_other_class> { } // compiler error CS0425, as expected
И снова я отрицаю ограничение:
class D : C, I<System.Object> { } // compiles
На этот раз работает нормально!
D d := new D();
d.Foo<Int32>();
d.Foo<String>();
d.Foo<Enum>();
d.Foo<IAppDomainSetup>();
d.Foo<InvalidCastException>();
Все идет, и это имеет смысл для меня. (То же самое с D
в уравнении или без него)
Так почему первый способ ломается?
Приложение:
Я забыл добавить, что существует простой обходной путь для TypeLoadException:
interface I
{
void Foo<T>();
}
class C<T1>
{
public void Foo<T2>() where T2 : T1 { }
}
class D : C<Object>, I
{
void I.Foo<T>()
{
Foo<T>();
}
}
Явная реализация I.Foo()
в порядке. Только неявная реализация вызывает исключение TypeLoadException. Теперь я могу сделать это:
I d = new D();
d.Foo<any_type_i_like>();
Но это все же частный случай. Попробуйте использовать что-нибудь другое, кроме System.Object, и это не скомпилируется. Я чувствую себя немного грязным, делая это, потому что я не уверен, что это намеренно работает таким образом.
Object
. Все экземпляры кучи имеют типы, производные отObject
, но место хранения типа значения просто содержит поля (общедоступные и частные) этого типа без какой-либо присоединенной информации о типе. Такой набор полей можно неявно преобразовать вObject
, но таковым не является. Обратите внимание, что ограничение чего-либо параметром универсального типа типаObject
эффективно добавляет ограничениеclass
. Обратите внимание, что общий параметр как с ограничением интерфейса, так и с ограничениемclass
будет приниматьstruct
реализации этого интерфейса, если они... - person supercat   schedule 13.06.2012