首页 > 解决方案 > 无法转换为 C# 中的基本泛型类型,但可以使用“as”运算符

问题描述

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

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

标签: c#generics

解决方案


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 时,您会得到一个与 int 具有相同值的 long ,而不是铸造错误。

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


推荐阅读