为什么';C#编译器捕获到一个InvalidCastException

为什么';C#编译器捕获到一个InvalidCastException,c#,exception,compiler-construction,compilation,.net,C#,Exception,Compiler Construction,Compilation,.net,可能重复: 据我所知,以下代码将始终编译,并且在运行时通过抛出InvalidCastException而另外始终失败 例如: public class Post { } public class Question : Post { } public class Answer : Post { public void Fail() { Post p = new Post(); Question q = (Question)p; // This

可能重复:

据我所知,以下代码将始终编译,并且在运行时通过抛出
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
    }
}
我的问题是

  • 如果我的假设是错误的,那么有人能提供一个例子来证明它们是如何错误的吗
  • 如果我的假设是正确的,那么为什么编译器不警告这个错误呢

  • 在某些情况下,
    Post
    可以转换为
    问题
    。通过执行强制转换,您告诉编译器,“我保证,这将起作用。如果不起作用,您可以抛出无效的强制转换异常。”

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

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

    强制转换是明确声明您比编译器更清楚这实际上是什么。您可能希望执行类似于
    as
    is
    关键字的操作?

    当您执行显式转换时,您告诉编译器“我知道一些您不知道的事情”


    实际上,您正在重写编译器的正常逻辑-
    p
    可能是一个
    问题(因此,编译器将编译),您告诉编译器您知道它是(即使它不是,因此运行时异常)。

    关键是
    p
    可能是一个
    问题,问题继承自
    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
        }
    }
    
    1) 你的假设是错误的。有些人总是可以为要从Post转换的问题实现显式转换运算符:

    public class Question`
    {
        // some class implementation
    
        public static explicit operator Question(Post p)
        {
            return new Question { Text = p.PostText };
        }
    }
    

    2) 显式强制转换是告诉编译器您比它更了解的一种方式。如果您不确定强制转换是否成功且不希望出现运行时异常时需要使用某些内容,请使用
    is
    作为
    运算符。

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

    在您的小示例中,很明显强制转换将失败,但编译器无法知道这一点。由于
    Post
    Question
    的超类型,您可以将
    Question
    分配给
    p
    ,并且由于您进行了转换,因此您声明愿意承担编译器的一些责任。如果您试图分配一个
    字符串
    或其他不属于同一继承分支的内容,编译器应该警告您。相反,您始终可以尝试将
    对象
    强制转换为任何类型


    但是让编译器抱怨您的特定示例意味着不允许强制转换

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

    编译器看到一个
    Post
    ,但它没有跟踪赋值,并且它知道可能:

    Post p = new Question();
    
    所以,它正常地通过它

    你知道你不能做:

    Question q = p;
    
    区别在于,您试图告诉编译器使用它所知道的来验证这一点,并且它知道
    Post
    不一定是
    问题


    在原始版本中,您告诉编译器“我知道是这样的,我会明确地设置它,滚开,如果我知道的是错误的,我会接受例外”,因此,它会听您的,滚开

    哇,杰里米,我最近遇到了这个问题!所以我制作了这个方便的扩展方法,它映射了两个共享一些相同属性的模型。其目的是在类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;
        }
    }
    
    公共静态类ObjectHelper
    {
    公共静态T转换(此对象源)
    {
    var destination=(T)Activator.CreateInstance(typeof(T));
    var sourcetype=source.GetType();
    var destinationtype=destination.GetType();
    var sourceProperties=sourcetype.GetProperties();
    var destinationproperties=destinationtype.GetProperties();
    var commonproperties=来自sourceProperties中的sp
    在新的{sp.Name,sp.PropertyType}equals上的DestinationProperties中加入dp
    新的{dp.Name,dp.PropertyType}
    选择新的{sp,dp};
    foreach(commonproperties中的变量匹配)
    {
    match.dp.SetValue(目标,match.sp.GetValue(源,null),null);
    }
    返回目的地;
    }
    }
    
    仅供参考,它可能仅在两个对象存在于同一部件中时才起作用


    大部分代码来自这里:

    允许这种转换的原因有两个

    首先,正如人们在其他答案中所说,cast操作符的意思是“我知道的比你多;我向你保证,这种转换将成功,如果我错了,抛出一个异常并使过程崩溃”。如果你对编译器撒谎,坏事就会发生;事实上,你没有做出保证,因此程序正在崩溃

    现在,如果编译器能告诉你在对它撒谎,那么它就能抓住你的谎言。编译器不需要任意聪明地抓住你的谎言!确定Base类型的表达式永远不会是派生类型所需的流分析是复杂的;要比我们已经实现的逻辑复杂得多,以捕获未分配的局部变量之类的内容。我们有更好的方法来花费我们的时间和精力,而不是提高编译器的能力,让你在明显的谎言中被识破

    因此,编译器通常只考虑表达式的类型,而不考虑可能的值。仅从类型分析不可能知道转换是否会成功。它可能会成功,因此是允许的。唯一不允许的强制转换是编译器知道将要执行的强制转换