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

Почему существует ограничение на явное приведение родословного типа класса, но нет ограничений на литье родового типа интерфейса?

Во время чтения документации Microsoft я наткнулся на такой интересный пример кода:

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

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

BTW - есть способ об ошибке компиляции, но это не устраняет логический беспорядок в моей голове:

class MyOtherClass
{...}

class MyClass<T> 
{

   void SomeMethod(T t)

   {
      object temp = t;
      MyOtherClass obj = (MyOtherClass)temp;

   }
}
4b9b3361

Ответ 1

Это то, что вы получаете в обычных условиях - без дженериков - при попытке бросить между классами без отношения наследования:

 public interface IA
 {
 }

 public class B
 {
 }

 public class C
 {
 }

 public void SomeMethod( B b )
 {
     IA o1 = (IA) b;   <-- will compile
     C o2 = (C)b;  <-- won't compile
 }

Таким образом, без ограничения, общий класс будет вести себя так, как будто между классами нет никакой связи.

Продолжение...

Ну, скажем, кто-то делает это:

 public class D : B, IA
 {
 }

И затем вызывает:

SomeMethod( new D() );

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

Помните, что класс D может быть хорошо написан кем-то, кто использует вашу сборку - спустя годы после того, как вы ее скомпилировали. Поэтому нет никаких шансов, что компилятор может отказаться от его компиляции. Он должен быть проверен во время выполнения.

Ответ 2

Большая разница заключается в том, что интерфейс гарантированно является ссылочным типом. Типы значений - создатели проблем. Он явно упоминается в Спецификации языка С#, глава 6.2.6, с превосходным примером, который демонстрирует проблему:


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

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

Если было разрешено прямое явное преобразование t в int, можно легко ожидать, что X.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.F(7) затем будет выдавать исключение во время выполнения, поскольку вложенный пакет не может быть преобразован непосредственно в длинный.

Ответ 3

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

// Compiles
ISomeInterface obj1 = (ISomeInterface)t;

// Сompiles too!
SomeClass obj2 = (SomeClass)(object)t;     

будет производить те же ошибки во время выполнения.

Таким образом, причина может быть: компилятор не знает, какие классы классов реализует, но он знает наследование классов (следовательно, метод (SomeClass)(object)t работает). Другими словами: недопустимое кастинг запрещается в CLR, единственное различие заключается в том, что в некоторых случаях его можно обнаружить во время компиляции, а в некоторых - нет. Основная причина этого, даже если компилятор знает обо всех интерфейсах класса, он не знает о нем потомки, которые могут его реализовать и действительны для T. Рассмотрим следующий сценарий:

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass<SomeClass> mc = new MyClass<SomeClass>();

            mc.SomeMethod(new SomeClassNested());

        }
    }

    public interface ISomeInterface
    {
    }

    public class SomeClass
    {

    }

    public class SomeClassNested : SomeClass, ISomeInterface
    {

    }

    public class MyClass<T>
    {
        public void SomeMethod(T t)
        {
            // Compiles, no errors at runtime
            ISomeInterface obj1 = (ISomeInterface)t;
        }
    }
}

Ответ 4

Я думаю, что разница между литьем на интерфейс и литье к классу заключается в том, что С# поддерживает множественное "наследование", только для интерфейсов. Что это значит? Компилятор способен во время компиляции определить, действителен ли класс для класса потому что С# не разрешает множественное наследование для классов.

С другой стороны, компилятор во время компиляции не знает, не ваш класс реализует интерфейс, используемый в трансляции. Зачем? Кто-то может наследовать ваш класс и реализовать интерфейс используемый в вашем литье. Таким образом, компилятор не знает об этом во время компиляции. (См. SomeMethod4() ниже).

Однако компилятор может определить, действительно ли листинг на интерфейс действителен, если ваш класс запечатан.

Рассмотрим следующий пример:

interface ISomeInterface
{}
class SomeClass
{}

sealed class SealedClass
{
}

class OtherClass
{
}

class DerivedClass : SomeClass, ISomeInterface
{
}

class MyClass
{
  void OtherMethod(SomeClass s)
  {
    ISomeInterface t = (ISomeInterface)s; // Compiles!
  }

  void OtherMethod2(SealedClass sc)
  {
    ISomeInterface t = (ISomeInterface)sc; // Does not compile!
  }

  void OtherMethod3(SomeClass c)
  {
    OtherClass oc = (OtherClass)c; // Does not compile because compiler knows 
  }                                // that SomeClass does not inherit from OtherClass!

  void OtherMethod4()
  {
    OtherMethod(new DerivedClass()); // In this case the cast to ISomeInterface inside
  }                                  // the OtherMethod is valid!
}

То же самое верно и для дженериков.

Надеюсь, это поможет.