Компилятору не удается преобразовать ограниченный универсальный тип

У меня есть класс с общим типом "G"

В моей модели класса у меня есть

public class DetailElement : ElementDefinition

Допустим, у меня есть такой метод

        public void DoSomething<G>(G generic)
            where G : ElementDefinition
        {
            if (generic is DetailElement)
            {
                ((DetailElement)generic).DescEN = "Hello people"; //line 1
                //////
                ElementDefinition element = generic;
                ((DetailElement)element).DescEN = "Hello again"; //line 3
                //////
                (generic as DetailElement).DescEN = "Howdy"; //line 5
            }
            else
            {
                //do other stuff
            }
        }

Компилятор сообщает об одной ошибке в строке 1:

Cannot convert type 'G' to 'DetailElement'

Но линия 3 работает нормально. Я могу обойти эту проблему, выполнив код, написанный в строке 5.

Я хотел бы знать, почему компилятор сообщает об ошибке в строке 1, а не об ошибке в строке 3, учитывая, что, насколько мне известно, они идентичны.

edit: я боюсь, что мне может не хватать какой-то важной части логики фреймворка

edit2: хотя решения для ошибки компилятора важны, мой вопрос о том, почему компилятор сообщает об ошибке в строке 1, а не в строке 3.


person Luis Filipe    schedule 08.10.2008    source источник


Ответы (4)


Если G было ограничено DetailElement (where G : DetailElement), то вы можете продолжить и привести G к ElementDefinition, т. е. "(ElementDefinition) generic". Но поскольку G может быть другим подклассом ElementDefinition, отличным от DetailElement во время выполнения, он не будет разрешен во время компиляции, где тип неизвестен и не поддается проверке.

В строке 3 известно, что тип, преобразованный из , является ElementDefinition, поэтому все, что вы делаете, — это преобразование вверх. Компилятор не знает, будет ли это успешное приведение во время выполнения, но он будет вам доверять. Компилятор не так доверяет дженерикам.

Оператор as в строке 5 также может возвращать значение null, и компилятор не проверяет тип статически, чтобы убедиться, что в этом случае он безопасен. Вы можете использовать as с любым типом, а не только с теми, которые совместимы с ElementDefinition.

Из Можно ли транслировать в параметры универсального типа и обратно? в MSDN:

Компилятор позволит вам неявно приводить параметры универсального типа к объекту или к типам, указанным в ограничениях.

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

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

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

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

 void SomeMethod<T>(T t) 
  { object temp = t;
    MyOtherClass obj = (MyOtherClass)temp;  
  }

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

Вместо риска приведения исключения лучше использовать операторы is или as. Оператор is возвращает true, если параметр универсального типа относится к запрошенному типу, а as выполнит приведение, если типы совместимы, и вернет null в противном случае.

public void SomeMethod(T t)
 {
   if(t is int) {...}

   string str = t as string;
   if(str != null) {...}
 }
person Mark Cidade    schedule 08.10.2008

Как правило, повышение уровня — это запах кода. Вы можете избежать этого путем перегрузки метода. Попробуй это:

public void DoSomething(DetailElement detailElement)
{
    // do DetailElement specific stuff
}

public void DoSomething<G>(G elementDefinition)
    where G : ElementDefinition
{
    // do generic ElementDefinition stuff
}

Затем вы можете воспользоваться перегрузкой метода, используя этот код:

DetailElement foo = new DetailElement();

DoSomething(foo); // calls the non-generic method
DoSomething((ElementDefinition) foo); // calls the generic method
person Michael Meadows    schedule 08.10.2008

Разве ваше предложение where не должно быть «where G : DetailElement»?

В написанном вами коде DetailElement является ElementDefinition, но ElementDefinition не обязательно является DetailElement. Таким образом, неявное преобразование является незаконным.

Существуют ли другие типы ElementDefinition, которые вы можете передать в этот метод? Если это так, они выдадут исключение, когда вы попытаетесь преобразовать их в экземпляры DetailElement.

РЕДАКТИРОВАТЬ:

Итак, теперь, когда вы изменили листинг кода, я вижу, что вы проверяете тип, чтобы убедиться, что это действительно DetailElement, прежде чем вводить этот блок кода. К сожалению, в том-то и дело, что неявно downcast нельзя сделать, даже если вы сами уже проверили типы. Я думаю, вам действительно следует использовать ключевое слово «как» в начале вашего блока:

DetailElement detail = generic as DetailElement;
if (detail == null) {
   // process other types of ElementDefinition
} else {
   // process DetailElement objects
}

Еще лучше, почему бы не использовать полиморфизм, чтобы позволить каждому типу ElementDefinition определять свой собственный метод DoSomething, а CLR позаботится о проверке типов и вызове метода за вас?

person benjismith    schedule 08.10.2008
comment
Для простоты я не привожу здесь весь код. я отредактирую это, чтобы быть правильным - person Luis Filipe; 08.10.2008

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

    public void DoSomething<G>(G generic)
        where G : ElementDefinition
    {
        DetailElement detail = generic as DetailElement;
        if (detail != null)
        {
            detail.DescEN = "Hello people";
        }
        else
        {
            //do other stuff
        }
    }

Другое возможное решение, которое я использовал, когда мне нужна была такая информация, — это использование временной объектной переменной.

DetailElement detail = (DetailElement)(object)generic;

Это работает, но форма as, вероятно, лучше всего.

person Guvante    schedule 08.10.2008