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

本文关键字:泛型类型 受约束 转换 编译器 | 更新日期: 2023-09-27 17:47:22

我有一个类,它的泛型类型为"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 行报告错误。

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

如果G被限制为DetailElementwhere G : DetailElement),那么你可以继续G投射到ElementDefinition,即"(ElementDefinition) generic"。但是G可能是ElementDefinition在运行时DetailElement之外的另一个子类,因此在类型未知且无法验证的编译时不允许这样做。

在第 3 行中,您投射的类型已知ElementDefinition所以你所做的只是向上投射。编译器不知道它在运行时是否会是一个成功的强制转换,但它会信任你。编译器对泛型不太信任。

第 5 行中的 as 运算符也可能返回 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;  
  }

不用说,这种显式强制转换是危险的,因为如果使用的具体类型而不是泛型类型参数不是从显式强制转换为的类型派生的,则可能会在运行时引发异常。

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

public void SomeMethod(T t)
 {
   if(t is int) {...}
   string str = t as string;
   if(str != null) {...}
 }

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

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
你的 where

子句不应该是"where G : DetailElement"吗?

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

是否有其他类型的元素定义可以传递到此方法中?如果是这样,当您尝试将它们强制转换为 DetailElement 实例时,它们将引发异常。

编辑:

好的,现在您已经更改了代码清单,我可以看到您正在检查类型以确保在输入该代码块之前它确实是 DetailElement。不幸的是,事实是,即使您已经自己检查了类型,您也不能隐式地向下投降。我认为您真的应该在块的开头使用"as"关键字:

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

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

如果你有很多担心的元素定义,这将导致更多的代码,但可能是你会得到的最光滑的,不涉及然后是胡说八道。

    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;

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