为什么铸造给出CS0030,而“as”给出CS0030?作品?

发布于 2024-12-05 22:58:57 字数 1229 浏览 0 评论 0原文

假设我有一个通用方法:

T Foo(T x) {
    return x;
}

到目前为止一切顺利。但如果它是一个哈希表,我想做一些特别的事情。 (我知道这是一个完全人为的示例。Foo() 也不是一个非常令人兴奋的方法。一起玩吧。)

if (typeof(T) == typeof(Hashtable)) {
    var h = ((Hashtable)x);  // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
}

该死。不过,公平地说,我实际上无法判断这是否应该是合法的 C#。好吧,如果我尝试用不同的方式来做呢?

if (typeof(T) == typeof(Hashtable)) {
    var h = x as Hashtable;  // works (and no, h isn't null)
}

这有点奇怪。根据 MSDN,表达式作为类型(除了计算表达式两次)与表达式是类型相同? (类型)表达式:(类型)null

如果我尝试使用文档中的等效表达式会发生什么?

if (typeof(T) == typeof(Hashtable)) {
    var h = (x is Hashtable ? (Hashtable)x : (Hashtable)null);  // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
}

我看到的强制转换和 as 之间唯一有记录的区别是“as 运算符仅执行引用转换和装箱转换”。也许我需要告诉它我正在使用引用类型?

T Foo(T x) where T : class {
    var h = ((Hashtable)x);  // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
    return x;
}

这是怎么回事?为什么 as 工作正常,而强制转换甚至无法编译?强制转换应该起作用,还是 as 不起作用,或者强制转换和 as 之间是否存在我找到的这些 MSDN 文档中没有的其他语言差异?

Suppose I have a generic method:

T Foo(T x) {
    return x;
}

So far so good. But I want to do something special if it's a Hashtable. (I know this is a completely contrived example. Foo() isn't a very exciting method, either. Play along.)

if (typeof(T) == typeof(Hashtable)) {
    var h = ((Hashtable)x);  // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
}

Darn. To be fair, though, I can't actually tell if this should be legal C# or not. Well, what if I try doing it a different way?

if (typeof(T) == typeof(Hashtable)) {
    var h = x as Hashtable;  // works (and no, h isn't null)
}

That's a little weird. According to MSDN, expression as Type is (except for evaluating expression twice) the same as expression is type ? (type)expression : (type)null.

What happens if I try to use the equivalent expression from the docs?

if (typeof(T) == typeof(Hashtable)) {
    var h = (x is Hashtable ? (Hashtable)x : (Hashtable)null);  // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
}

The only documented difference between casting and as that I see is "the as operator only performs reference conversions and boxing conversions". Maybe I need to tell it I'm using a reference type?

T Foo(T x) where T : class {
    var h = ((Hashtable)x);  // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
    return x;
}

What's going on? Why does as work fine, while casting won't even compile? Should the cast work, or should the as not work, or is there some other language difference between casting and as that isn't in these MSDN docs I found?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

逐鹿 2024-12-12 22:58:57

Ben 的回答基本上击中要害,但稍微扩展一下:

这里的问题是,人们有一种自然的期望,即如果给定类型,泛型方法将执行与等效的非泛型方法相同的操作在编译时。在您的特定情况下,人们会期望如果 T 很短,那么 (int)t 应该做正确的事情 - 将 Short 转换为 int。并且 (double)t 应该将 Short 变为 double。如果 T 是字节,那么 (int)t 应该将字节转换为 int,而 (double)t 应该将字节转换为 double...现在也许你开始看到问题所在。我们必须生成的通用代码基本上必须在运行时再次启动编译器并进行完整的类型分析,然后动态生成代码以按预期进行转换

这可能是昂贵的;我们在 C# 4 中添加了该功能,如果这是您真正想要的,您可以将对象标记为“动态”类型,并且编译器的一个精简版本将在运行时再次启动并为您执行转换逻辑。

但那种昂贵的东西通常不是人们想要的。

“as”逻辑比强制转换逻辑简单得多,因为它不需要处理除装箱、拆箱和引用转换之外的任何转换。它不必处理用户定义的转换,也不必处理花哨的表示更改转换,例如将一字节数据结构转换为八字节数据结构的“字节到双精度”等。

这就是为什么通用代码中允许使用“as”,但不允许使用强制转换。

综上所述:你几乎肯定做错了。如果您必须在通用代码中进行类型测试,您的代码就不是通用的。这是一种非常糟糕的代码味道。

Ben's answer basically hits the nail on the head, but to expand on that a bit:

The problem here is that people have a natural expectation that a generic method will do the same thing that the equivalent non-generic method would do if given the types at compile time. In your particular case, people would expect that if T is short, then (int)t should do the right thing -- turn the short into an int. And (double)t should turn the short into a double. And if T is byte, then (int)t should turn the byte into an int, and (double)t should turn the byte into a double... and now perhaps you begin to see the problem. The generic code we'd have to generate would basically have to start the compiler again at runtime and do a full type analysis, and then dynamically generate the code to do the conversion as expected.

That is potentially expensive; we added that feature in C# 4 and if that's what you really want, you can mark the objects as being of type "dynamic" and a little stripped-down version of the compiler will start up again at runtime and do the conversion logic for you.

But that expensive thing is typically not what people want.

The "as" logic is far less complicated than the cast logic because it does not have to deal with any conversions other than boxing, unboxing and reference conversions. It does not have to deal with user-defined conversions, it does not have to deal with fancy representation-changing conversions like "byte to double" that turn one-byte data structures into eight-byte data structures, and so on.

That's why "as" is allowed in generic code but casts are not.

All that said: you are almost certainly doing it wrong. If you have to do a type test in generic code your code is not generic. This is a really bad code smell.

寄人书 2024-12-12 22:58:57

C# 中的强制转换运算符可以:

  • box/unbox
  • upcast/downcast
  • 调用用户定义的转换运算符

as Hashtable 始终表示第二个。

通过消除具有约束的值类型,您已经淘汰了选项 1,但它仍然不明确。


以下是两种都有效的“最佳”方法:

Hashtable h = x as Hashtable;
if (h != null) {
    ...
}

if (x is Hashtable) {
    Hashtable h = (Hashtable)(object)x;
    ...
}

第一种只需要一次类型测试,因此非常有效。 JIT 优化器识别第二个,并将其视为第一个(至少在处理非泛型类型时,我不确定这种特殊情况。)

The cast operator in C# can:

  • box/unbox
  • upcast/downcast
  • call a user-defined conversion operator

as Hashtable always means the second.

By eliminating value types with the constraint, you've knocked out option 1, but it's still ambiguous.


Here are the two "best" approaches that both work:

Hashtable h = x as Hashtable;
if (h != null) {
    ...
}

or

if (x is Hashtable) {
    Hashtable h = (Hashtable)(object)x;
    ...
}

The first needs only one type test, so it's very efficient. And the JIT optimizer recognizes the second one, and treats it like the first (at least when dealing with non-generic types, I'm not sure about this particular case.)

碍人泪离人颜 2024-12-12 22:58:57

“C# 编译器只允许您将泛型类型参数隐式转换为 Object 或约束指定的类型,如代码块 5 所示。这种隐式转换是类型安全的,因为任何不兼容性都会在编译时发现。”

请参阅有关泛型和转换的部分:
http://msdn.microsoft.com/ en-us/library/ms379564(v=vs.80).aspx#csharp_generics_topic5

"The C# compiler only lets you implicitly cast generic type parameters to Object, or to constraint-specified types, as shown in Code block 5. Such implicit casting is type safe because any incompatibility is discovered at compile-time."

See the section on Generics and Casting:
http://msdn.microsoft.com/en-us/library/ms379564(v=vs.80).aspx#csharp_generics_topic5

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文