явное приведение параметров универсального типа к любому интерфейсу - PullRequest
35 голосов
/ 20 июня 2011

In Generics FAQ: Best Practices говорит:

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

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

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

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

Ответы [ 2 ]

50 голосов
/ 20 июня 2011

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

Параметры:

  • Сначала приведение к объекту:

    SomeClass obj2 = (SomeClass) (object) t;
    
  • Используйте as вместо:

    SomeClass obj2 = t as SomeClass;
    

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

РЕДАКТИРОВАТЬ: Обоснование этого приведено в разделе 6.2.7 спецификации C # 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) вызовет исключение во время выполнения, так как упакованный int не может быть напрямую преобразован в long.

2 голосов
/ 20 июня 2011

В принципе наследования C # интерфейсы могут наследоваться несколько раз, но класс - только один раз. Поскольку наследование от интерфейсов имеет сложную иерархию, .NET Framework не должен обеспечивать универсальный тип T конкретным интерфейсом во время компиляции. (РЕДАКТИРОВАТЬ) Напротив, классу может быть предоставлен конкретный класс с объявлением ограничения типа при компиляции в виде следующего кода.

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