【问题标题】:Why is this cast invalid when `x as Y` works fine?当 `x as Y` 工作正常时,为什么这个强制转换无效?
【发布时间】:2012-05-25 23:32:23
【问题描述】:

我昨天偶然发现了这个奇怪的情况,t as D 返回一个非空值,但(D)t 导致编译器错误。

因为我很着急,所以我只使用了t as D 并继续,但我很好奇为什么演员表无效,因为t 真的是D。谁能解释一下为什么编译器不喜欢演员表?

class Program
{
    public class B<T> where T : B<T> { }

    public class D : B<D> { public void M() { Console.Out.WriteLine("D.M called."); } }

    static void Main() { M(new D()); }

    public static void M<T>(T t) where T : B<T>
    {
        // Works as expected: prints "D.M called."
        var d = t as D;
        if (d != null)
            d.M();

        // Compiler error: "Cannot cast expression of type 'T' to type 'D'."
        // even though t really is a D!
        if (t is D)
            ((D)t).M();
    }
}

编辑: 玩玩,我认为这是一个更清晰的例子。在这两种情况下,t 都被限制为B,并且可能是D。但是泛型的情况不会编译。在确定强制转换是否合法时,C# 是否只是忽略了通用约束?即使它确实忽略了它,t 仍然可能是D;那么为什么这是编译时错误而不是运行时异常呢?

class Program2
{
    public class B { }

    public class D : B { public void M() { } }

    static void Main()
    {
        M(new D());
    }

    public static void M(B t)
    {
        // Works fine!
        if (t is D)
            ((D)t).M();
    }

    public static void M<T>(T t) where T : B
    {
        // Compile error!
        if (t is D)
            ((D)t).M();
    }
}

【问题讨论】:

  • (D)(object)t 作品
  • this link 的评论中,我找到了指向your answer 的链接。本质上,有些变量类型不能转换为其他类型(更具体地说,有关于转换盒装类型的规则)。由于编译器在编译时不知道 T 是什么,它必须谨慎行事并拒绝强制转换。
  • @BenVoigt 确实如此,但我不知道为什么需要 (object)...
  • @KirkWoll 该问题的公认答案是“[T]他的编译器不知道 T 是字符串。因此,它不允许你转换。[...]你需要转换为对象,(任何 T 都可以转换为对象),然后从那里转换为字符串”。那有什么不同?编译器也不知道某些任意的objectstring ......事实上,在我看来,“编译器不能保证X 是Y”首先是显式转换的重点!

标签: c# casting


【解决方案1】:

在您的第二个示例中,您可以更改

((D)t).M();

((D)((B)t)).M();

您可以从B 转换为D,但不一定从“某物是B”转换为D。 "Something that is a B" 可能是 A,例如,如果 A : B

当您可能在层次结构中从一个子级跳转到另一个子级时,就会出现编译器错误。您可以向上和向下转换层次结构,但不能跨越它。

((D)t).M();       // potentially across, if t is an A
((D)((B)t)).M();  // first up the hierarchy, then back down

还请注意,如果 t 实际上不是 D,您可能仍然会遇到 ((D)((B)t)).M(); 的运行时错误。 (不过,您的 is 检查应该可以防止这种情况发生。)

(另请注意,在这两种情况下,编译器都不会考虑if (t is D) 检查。)

最后一个例子:

class Base { }
class A : Base { }
class C : Base { }

...
A a = new A();
C c1 = (C)a;         // compiler error
C c2 = (C)((Base)a); // no compiler error, but a runtime error (and a resharper warning)

// the same is true for 'as'
C c3 = a as C;           // compiler error
C c4 = (a as Base) as C; // no compiler error, but always evaluates to null (and a resharper warning)

【讨论】:

  • 那么,为什么as 会编译?在这种情况下,cast 和 as 有什么区别(除了它的运行时行为)?
  • as 在相同情况下给出相同的错误。编辑:实际上,在 OP 的示例中它没有。将不得不再玩一些。
  • as 是完全不同的动物。见this msdn link - 相当于expression is type ? (type)expression : (type)null
【解决方案2】:

改成

public static void M<T>(T t) where T : D

如果您想强制 T 必须是 D 类型,这是适当的限制。

如果您的限制将 T 定义为 T 的 D,它将无法编译。

例如:您不能投射 List&lt;int&gt; to int,反之亦然

【讨论】:

  • 他不是问怎么解决,而是问为什么会被编译器拒绝。
【解决方案3】:

您的模板函数有一个约束,要求 TB&lt;T&gt;

因此,当您的编译器尝试将 T 类型的对象 t 转换为 D 时,它无法做到。因为T 保证为B&lt;T&gt;,但不是D

如果您添加一个约束以要求 TD,这将起作用。即where T: B&lt;T&gt;, D 或简单的where T: D 这也保证TB&lt;T&gt;,因为继承链。

问题的第二部分:当您调用t as D 时,它会在运行时进行检查。并且,在运行时,使用多态验证了t可以转换为D类型,并且没有错误。

注意:顺便说一下,这段代码太奇怪了。你确定你在做什么?

【讨论】:

    猜你喜欢
    • 2017-10-19
    • 1970-01-01
    • 1970-01-01
    • 2014-11-20
    • 2012-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-14
    相关资源
    最近更新 更多