简体   繁体   English

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

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

why casts don't work on constrained generic type as shown below?为什么强制转换不适用于约束泛型类型,如下所示?

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 is not constrained to be assignable to T. For example: 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
    }
}

Just because T is constrained to be a B doesn't mean you can cast any descendant of B to any possible T.仅仅因为 T 被限制为 B 并不意味着您可以将 B 的任何后代强制转换为任何可能的 T。

Why not allow that anyway?为什么不允许呢? In my thinking, it doesn't make any sense.在我看来,这没有任何意义。 If you wanted to make sure B1 is assignable to T, you shouldn't be using generics.如果您想确保 B1 可分配给 T,则不应使用泛型。 It's way too easy to make this kind of mistake, and if possible, you should avoid casting around generics in the first place.犯这种错误太容易了,如果可能,您应该首先避免强制转换泛型。 They're made (primarily) to make static typing more powerful, while keeping the type safety (and performance benefits).它们(主要)是为了使静态类型更强大,同时保持类型安全(和性能优势)。

However, there's definitely obviously wrong cases that don't get caught, because但是,肯定有明显错误的情况不会被抓住,因为

T b2 = (T)new B();

does compile, even though it actually has the same problem and you will get a runtime cast error if T isn't B.确实编译,即使它实际上有同样的问题,你会得到一个运行时错误铸如果T是不是B.

Of course, in cases like this, it's helpful to check the C# specification , and clearly enough, that says:当然,在这种情况下,检查C# 规范会很有帮助,而且很清楚,它说:

The above rules do not permit a direct explicit conversion from an unconstrained type parameter to a non-interface type, which might be surprising.上述规则不允许从不受约束的类型参数直接显式转换为非接口类型,这可能令人惊讶。 The reason for this rule is to prevent confusion and make the semantics of such conversions clear.这条规则的原因是为了防止混淆并使此类转换的语义清晰。

While this only really seems to make sense for value-types, this explains both why you can do a direct cast of (T)new B();虽然这似乎只对值类型有意义,但这解释了为什么你可以直接执行(T)new B(); , and why you cannot do (T)new B1(); ,以及为什么你不能做(T)new B1(); - even though both have the same problem with T not necessarily being B. - 即使两者都有相同的问题,T 不一定是 B。

Remember, operators in C# are not virtual - they depend on the compile-time type of the expressions.请记住,C#中的运算符不是虚拟的- 它们取决于表达式的编译时类型。 For value type arguments, you actually get a variant for each value type you use (ie List<long> uses different code than List<int> ) - so you get the correct cast, like when casting from int to long, you get a long with the same value as the int, rather than a casting error.对于值类型参数,您实际上会为您使用的每个值类型获得一个变体(即List<long>使用与List<int>不同的代码) - 因此您会获得正确的转换,例如从 int 转换为 long 时,您会得到一个long 与 int 具有相同的值,而不是转换错误。

For reference types, this isn't true.对于引用类型,这不是真的。 In your case, you could have a custom cast operator from B to B that would actually be invoked in the (T) new B() case, but not a cast from B1 to B, because the reified generic type for G<B> and G<B1> is actually the same.在你的情况下,你可以有一个从 B 到 B 的自定义转换运算符,它实际上会在(T) new B()情况下被调用,但不是从 B1 到 B 的转换,因为G<B>的具体化泛型类型和G<B1>其实是一样的。 Since this is an implementation detail that can change at any time, you really want to avoid the confusion and potential change of behaviour.由于这是一个可以随时更改的实现细节,您确实希望避免混淆和潜在的行为变化。

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

Just because both T and B1 are derived from B doesn't mean B1 is assignable to T. They both are assignable to B, so仅仅因为 T 和 B1 都源自 B 并不意味着 B1 可以赋值给 T。它们都可以赋值给 B,所以

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

. .

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

As explained above, two classes with a common base class do not ensure type compatibility so explicit casting won't work either如上所述,具有公共基类的两个类不能确保类型兼容性,因此显式转换也不起作用

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

it works but you probably assign null to b3 since B1 is not convertible to T. The as operator just returns null instead of emitting a compiler warning or throwing an exception.它有效,但您可能将 null 分配给 b3,因为 B1 不能转换为 T。 as 运算符只返回 null 而不是发出编译器警告或抛出异常。

The answer is, because there are no guarantees you supplied B1 as the generic parameter, you can't do this with generics, lets explore the reason why...答案是,因为不能保证你提供B1作为泛型参数,你不能用泛型来做到这一点,让我们探讨一下为什么......

Given给定的

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

Now, what if you used your class like this现在,如果你像这样使用你的类怎么办

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

Constraints are a minimum contract, that's it.约束是一个最低限度的契约,就是这样。 They allow you to use the generic parameter on that contract you specified.它们允许您在指定的合约上使用泛型参数。

Its exactly the same as why you cant do the following这和你为什么不能做下面的事情完全一样

Dog dog = new Cat();

Even if they both inherit from Animal , does not mean they are the same... They have a different memory layout internally, they have different methods and properties, they cant be mashed together statically typed like this.即使它们都继承自Animal ,也不意味着它们是相同的......它们内部具有不同的内存布局,它们具有不同的方法和属性,它们不能像这样静态类型混合在一起。

In short, you likely need to rethink your problem.简而言之,您可能需要重新考虑您的问题。

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

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