为什么C#编译器没有捕获InvalidCastException

时间:2011-11-17 15:19:55

标签: c# exception compiler-construction compilation .net

  

可能重复:
  Compile-time and runtime casting c#

据我了解,以下代码将始终编译,并且在运行时另外始终会抛出InvalidCastException

示例:


public class Post { }
public class Question : Post { }
public class Answer : Post 
{
    public void Fail()
    {
        Post p = new Post();
        Question q = (Question)p; // This will throw an InvalidCastException
    }
}

我的问题是......

  1. 如果我的假设没有,那么有人可以提供一个证明他们如何离开的例子吗?
  2. 如果我的假设是正确的,那么编译器为什么不警告这个错误?

8 个答案:

答案 0 :(得分:14)

允许进行此转换有几个原因。

首先,正如人们在其他答案中所说的那样,演员操作符意味着“我知道的比你做得更多;我向你保证,这种转换会成功,如果我错了,就抛出异常并使进程崩溃”。如果你对编译器撒谎,那么坏事就会发生;实际上你做出了这样的保证,而 程序因此而崩溃。

现在,如果编译器可以告诉你对它撒谎,那么它可以让你陷入困境。编译器不需要任意聪明地抓住你的谎言!确定Base类型 never 的表达式是否为Derived类型所需的流分析是复杂的;比我们已经实现的逻辑要复杂得多,比如未分配的局部变量。我们有更好的方法来花费我们的时间和精力,而不是提高编译器在明显谎言中抓住你的能力。

因此,编译器通常只会推断表达式的类型,而不是可能的值。仅从类型分析中就不可能知道转换是否会成功。它可能成功,所以它是允许的。唯一不允许的强制转换是编译器知道的总是从类型分析中失败

其次,可能(Derived)(new Base())其中Derived是一种实现Base类型并且 not 在运行时失败的类型。 (Base)(new Base())也可能在运行时失败并出现无效的强制转换异常!真实的事实!这些是非常罕见的情况,但 是可能的。

有关详细信息,请参阅有关此主题的文章:

http://blogs.msdn.com/b/ericlippert/archive/2007/04/16/chained-user-defined-explicit-conversions-in-c.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/04/18/chained-user-defined-explicit-conversions-in-c-part-two.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/04/20/chained-user-defined-explicit-conversions-in-c-part-three.aspx

答案 1 :(得分:11)

在某些情况下,Post可以投放到Question。通过执行强制转换,你告诉编译器,“这将有效,我保证。如果没有,你可以抛出无效的强制转换异常。”

例如,此代码可以正常工作:

    Post p = new Question();
    Question q = (Question)p;

演员明确表示你比编译器更了解这实际上是什么。您可能希望执行类似asis关键字的操作?

答案 2 :(得分:8)

重点是p可以是Question,因为问题会从Post继承。
请考虑以下事项:

public class Post { }
public class Question : Post { }
public class Banana { }

static class Program {
    public static void Main(params string[] args) {
        Post p = new Question();
        Question q = (Question)p; // p IS a Question in this case
        Banana b = (Banana)p; // this does not compile
    }
}

答案 3 :(得分:6)

当你做一个明确的演员表时,你告诉编译器“我知道你不知道的东西”。

你本质上重写了编译器的正常逻辑 - p 可能Question(因此,编译器会编译),你告诉编译器你知道它(即使它不是,因此运行时异常)。

答案 4 :(得分:2)

1)你的假设是假的。有人总是可以实现一个显式转换运算符,用于转换来自Post:

public class Question`
{
    // some class implementation

    public static explicit operator Question(Post p)
    {
        return new Question { Text = p.PostText };
    }
}

2)显式转换是告诉编译器你比它更了解的方式。如果您在不确定演员表是否成功且不想要运行时异常时想要使用某些内容,请使用isas运算符。

答案 5 :(得分:1)

编译器将p视为变量,因此,它不会尝试跟踪它的值。如果确实如此,分析整个应用程序需要很长时间。一些静态分析工具与FxCop类似。

编译器看到Post,但它没有跟踪任务,它知道可能:

Post p = new Question();

所以,它正常通过。

你知道你做不到:

Question q = p;

不同之处在于,您试图告诉编译器使用它所知道的来验证这一点,并且它知道Post不一定是Question

在原始版本中,您告诉编译器“我知道它是,并且我将明确地设置它,开始我的方式,如果我知道错误,我将采取例外”,所以,它会听取你的意见然后离开你的路!

答案 6 :(得分:0)

您的假设是正确的:它将编译并在运行时失败。

在你的小例子中,很明显演员表会失败,但编译器无法知道这一点。由于PostQuestion的超类型,因此您可以将Question分配给p,并且自从您进行投射后,您确实表示愿意从编译器中承担一些责任。如果你一直试图分配一个string或其他不属于同一继承分支的东西,编译器应该警告你。相反,您总是可以尝试将object转换为任何类型。

但让编译器抱怨你的具体例子意味着不允许任何强制转换。

答案 7 :(得分:0)

哇,杰里米,我最近碰到了这个问题!所以我制作了这个方便的扩展方法,它映射了两个共享几个相同属性的模型。当A类继承自B类以将B类映射到A类时,目的是使用它。希望您觉得它有用!

public static class ObjectHelper
{
    public static T Cast<T>(this Object source)
    {
        var destination = (T)Activator.CreateInstance(typeof(T));

        var sourcetype = source.GetType();
        var destinationtype = destination.GetType();

        var sourceProperties = sourcetype.GetProperties();
        var destionationProperties = destinationtype.GetProperties();

        var commonproperties = from sp in sourceProperties
                               join dp in destionationProperties on new { sp.Name, sp.PropertyType } equals
                                   new { dp.Name, dp.PropertyType }
                               select new { sp, dp };

        foreach (var match in commonproperties)
        {
            match.dp.SetValue(destination, match.sp.GetValue(source, null), null);
        }

        return destination;
    }
}

仅供参考,它可能仅在两个对象存在于同一个程序集中时才有效。

大部分代码来自此处:Mapping business Objects and Entity Object with reflection c#