编译器无法转换受约束的泛型类型

时间:2008-10-08 17:34:28

标签: c# generics casting constraints

我有一个具有通用类型“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行中的错误,因为据我所知,它们是相同的。

编辑:恐怕我可能会遗漏一些重要的框架逻辑

edit2:虽然编译器错误的解决方案很重要,但我的问题是编译器为什么在第1行而不是第3行报告错误。

4 个答案:

答案 0 :(得分:7)

如果G被限制为DetailElementwhere G : DetailElement),那么您可以继续将G投射到ElementDefinition,即“(ElementDefinition) generic” 。但是因为G在运行时可能是ElementDefinition而不是DetailElement的另一个子类,所以在编译时它不会允许它在类型未知和无法验证的情况下。

在第3行中,您从投出的类型已知为ElementDefinition,因此您所做的只是上传。编译器不知道它是否会在运行时成为一个succcesful演员,但它会相信你。编译器不太相信泛型。

第5行中的 as 运算符也可能返回null,并且编译器不会静态检查类型以查看在这种情况下它是否安全。您可以将as任何类型一起使用,而不仅仅是与ElementDefinition兼容的类型。

来自MSDN上的 Can I Cast to and from Generic Type Parameters?

  

编译器只允许您将泛型类型参数隐式转换为对象或约束指定的类型。

     

这种隐式转换当然是类型安全的,因为在编译时会发现任何不兼容性。

     

编译器将允许您将泛型类型参数显式地转换为任何接口,但不能转发给类:

   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;  
  }
     

毋庸置疑,这种显式转换很危险,因为如果使用的具体类型而不是泛型类型参数不是从您明确转换为的类型派生的,那么它可能会在运行时抛出异常。

     

更好的方法是使用isas运算符,而不是冒着转换异常的风险。如果泛型类型参数是查询类型,则is运算符返回true,如果类型兼容,as将执行强制转换,否则返回null。

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

   string str = t as string;
   if(str != null) {...}
 }

答案 1 :(得分:1)

你的where子句不应该是“G:DetailElement”吗?

在您编写的代码中,DetailElement是ElementDefinition,但ElementDefinition不一定是DetailElement。所以隐式转换是非法的。

您是否可以将其他类型的ElementDefinition传递给此方法?如果是这样,当您尝试将它们转换为DetailElement实例时,它们将抛出异常。

编辑:

好的,现在您已经更改了代码清单,我可以看到您在输入该代码块之前检查类型以确保它确实是DetailElement。不幸的是,事实是即使你已经自己检查了这些类型,你也不能暗中贬低。我认为你真的应该在你的块的开头使用“as”关键字:

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

更好的是,为什么不使用多态来允许每种ElementDefinition定义自己的DoSomething方法,让CLR为你处理类型检查和方法调用?

答案 2 :(得分:1)

通常,向上转换是一种代码气味。您可以通过方法重载来避免它。试试这个:

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

答案 3 :(得分:0)

如果你有很多你担心的ElementDefinitions,这会导致更多的代码,但是你可能得到的最简单的代码就是废话。

    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
        }
    }

我在需要此类信息时使用的另一种可能的解决方案,在临时对象变量的loo中。

DetailElement detail = (DetailElement)(object)generic;

它有效,但形式可能是最好的。