繁体   English   中英

无法在 C# 中强制转换为基本泛型类型,但可以使用“as”运算符

[英]Cannot cast to base generic type in C# but can use 'as' operator

为什么强制转换不适用于约束泛型类型,如下所示?

class B { }

class B1 : B { }

class G<T> where T : B
{ 
    void x()
    {
        T b1 = new B1();    // why implicit conversion doesn't compile?
        T b2 = (T)new B1(); // why explicit conversion doesn't compile either?
        T b3 = new B1() as T;  // this works!
    }
}

B1不限于可分配给 T。例如:

void Main()
{
    new G<C1>().x();
}

class B { }

class B1 : B { }

class C1 : B { }

class G<T> where T : B
{ 
    public void x()
    {
        T b3 = new B1() as T;
        
        b3.Dump(); // null, because B1 cannot be converted to C1
    }
}

仅仅因为 T 被限制为 B 并不意味着您可以将 B 的任何后代强制转换为任何可能的 T。

为什么不允许呢? 在我看来,这没有任何意义。 如果您想确保 B1 可分配给 T,则不应使用泛型。 犯这种错误太容易了,如果可能,您应该首先避免强制转换泛型。 它们(主要)是为了使静态类型更强大,同时保持类型安全(和性能优势)。

但是,肯定有明显错误的情况不会被抓住,因为

T b2 = (T)new B();

确实编译,即使它实际上有同样的问题,你会得到一个运行时错误铸如果T是不是B.

当然,在这种情况下,检查C# 规范会很有帮助,而且很清楚,它说:

上述规则不允许从不受约束的类型参数直接显式转换为非接口类型,这可能令人惊讶。 这条规则的原因是为了防止混淆并使此类转换的语义清晰。

虽然这似乎只对值类型有意义,但这解释了为什么你可以直接执行(T)new B(); ,以及为什么你不能做(T)new B1(); - 即使两者都有相同的问题,T 不一定是 B。

请记住,C#中的运算符不是虚拟的- 它们取决于表达式的编译时类型。 对于值类型参数,您实际上会为您使用的每个值类型获得一个变体(即List<long>使用与List<int>不同的代码) - 因此您会获得正确的转换,例如从 int 转换为 long 时,您会得到一个long 与 int 具有相同的值,而不是转换错误。

对于引用类型,这不是真的。 在你的情况下,你可以有一个从 B 到 B 的自定义转换运算符,它实际上会在(T) new B()情况下被调用,但不是从 B1 到 B 的转换,因为G<B>的具体化泛型类型和G<B1>其实是一样的。 由于这是一个可以随时更改的实现细节,您确实希望避免混淆和潜在的行为变化。

T b1 = new B1();    // why implicit conversion doesn't compile?

仅仅因为 T 和 B1 都源自 B 并不意味着 B1 可以赋值给 T。它们都可以赋值给 B,所以

B b1 = new B1();  // should work

.

T b2 = (T)new B1(); // why explicit conversion doesn't compile either?

如上所述,具有公共基类的两个类不能确保类型兼容性,因此显式转换也不起作用

   T b3 = new B1() as T;  // this works!

它有效,但您可能将 null 分配给 b3,因为 B1 不能转换为 T。 as 运算符只返回 null 而不是发出编译器警告或抛出异常。

答案是,因为不能保证你提供B1作为泛型参数,你不能用泛型来做到这一点,让我们探讨一下为什么......

给定的

class Animal { }
class Dog : Animal { }
class Cat : Animal { }

class Something<T> where T : Animal
{ 
   public T Animal {get;set;}

   void x()
   {
       Animal = (T)new Cat(); 
   }
}

现在,如果你像这样使用你的类怎么办

var dog = new Something<Dog>();

dog.X() // internally you are trying to cast a Cat to a Dog

Dog dog = dog.Animal; // now you just tried to mash a Cat into a dog
Dog.Bark() // what the...

约束是一个最低限度的契约,就是这样。 它们允许您在指定的合约上使用泛型参数。

这和你为什么不能做下面的事情完全一样

Dog dog = new Cat();

即使它们都继承自Animal ,也不意味着它们是相同的......它们内部具有不同的内存布局,它们具有不同的方法和属性,它们不能像这样静态类型混合在一起。

简而言之,您可能需要重新考虑您的问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM