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

Явно накладывать параметры типового типа на любой интерфейс

В Часто задаваемые вопросы по обобщениям: лучшие практики:

Компилятор позволит вам явно использовать общие параметры типа для любого интерфейса, но не для класса:

interface ISomeInterface
{...}
class SomeClass
{...}
class MyClass<T> 
{
   void SomeMethod(T t)
   {
      ISomeInterface obj1 = (ISomeInterface)t;//Compiles
      SomeClass      obj2 = (SomeClass)t;     //Does not compile
   }
}

Я вижу ограничение, разумное для обоих классов и интерфейсов, если класс/интерфейс не указан как тип ограничения.

Так почему такое поведение, почему это разрешено для интерфейсов?

4b9b3361

Ответ 1

Я считаю, что это потому, что приведение в SomeClass может означать любое количество вещей в зависимости от того, какие конверсии доступны, тогда как приведение к ISomeInterface может быть только ссылкой или преобразованием бокса.

Параметры:

  • Перенесите объект первым:

    SomeClass obj2 = (SomeClass) (object) t;
    
  • Вместо этого используйте as:

    SomeClass obj2 = t as SomeClass;
    

Очевидно, что во втором случае вам также потребуется выполнить проверку недействительности позже, если t не является SomeClass.

EDIT: аргументы в пользу этого приведены в разделе 6.2.7 спецификации С# 4:

Вышеупомянутые правила не позволяют прямое явное преобразование из параметра неограниченного типа в тип без интерфейса, что может быть неожиданным. Причина этого правила заключается в том, чтобы предотвратить путаницу и сделать семантику таких преобразований понятными. Например, рассмотрим следующее выражение:

class X<T>
{
    public static long F(T t) {
        return (long)t; // Error 
    }
} 

Если было разрешено прямое явное преобразование t в int, можно легко ожидать, что X<int>.F(7) вернет 7L. Однако это не так, потому что стандартные числовые преобразования учитываются только тогда, когда типы известны как числовые во время привязки. Чтобы сделать семантику понятной, вместо этого должен быть записан вышеприведенный пример:

class X<T>
{
    public static long F(T t) {
        return (long)(object)t; // Ok, but will only work when T is long
    }
}

Теперь этот код будет компилироваться, но выполнение X<int>.F(7) затем генерирует исключение во время выполнения, так как вложенный индекс не может быть преобразован непосредственно в длинный.

Ответ 2

В принципе наследования С# интерфейсы могут наследоваться несколько раз, но класс один раз. Поскольку наследование от интерфейсов имеет сложную иерархию, .net-инфраструктуре не требуется гарантировать, что общий тип T является конкретным интерфейсом во время компиляции. (EDIT) Напротив, классу может быть обеспечен определенный класс с объявлением ограничения типа при компиляции как следующий код.

class MyClass<T> where T : SomeClass
{
   void SomeMethod(T t)
   {
      ISomeInterface obj1 = (ISomeInterface)t;
      SomeClass      obj2 = (SomeClass)t;     
   }
}