为什么C#编译器无法捕获InvalidCastException异常?

10

可能是重复问题:
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. 如果我的假设是正确的,那么为什么编译器不会警告这个错误呢?

4
为什么您指望编译器遵循所有可能的代码路径来确定在转换之前p没有被修改? - John Saunders
即使没有改变,Post也可以实现一个隐式操作符将自己转换为Question或反之亦然。 - PVitt
4
铸模是给牛仔的,上公牛去骑吧,宝贝。 - kenny
正如其他人所指出的那样,用静态分析来确定这将失败是非常复杂的。一些较新的静态分析工具可以找到类似这样的问题,但它足够复杂,以至于在编译期间自然地作为故障而失败是不可能的。 - Dan Bryant
8个回答

15

这种转换允许的原因有几个。

首先,正如其他答案中所说,强制转换运算符的意思是“我知道比你更多;我保证此转换将成功,如果我错了,请抛出异常并崩溃进程”。如果你在欺骗编译器,会发生糟糕的事情;实际上你没有作出那个保证,程序正好因此而崩溃。

其次,如果编译器能够发现你在欺骗它,那么它可以发现你的欺骗。编译器不需要在抓住你的谎言方面任意聪明!要确定类型为Base的表达式永远不会是Derived类型,所需的流分析很复杂;远比我们已经实现的捕捉未分配本地变量之类的东西的逻辑复杂得多。我们有更好的方式来花费时间和精力,而不是改进编译器在显然的谎言中抓住你的能力。

因此,编译器通常仅考虑表达式的类型,而不考虑可能的值。仅从类型分析中无法知道转换是否成功。它可能会成功,所以是允许的。唯一不允许的强制转换是编译器从类型分析中知道将始终失败的强制转换。

其次,(Derived)(new Base()) 是可能的,其中Derived是实现类型Base的类型,并且它在运行时不会失败。也有可能让(Base)(new Base()) 在运行时失败,出现无效的强制转换异常!真实的事实!这些情况极为罕见,但是它们是可能的。

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


11

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

例如,以下代码将正常工作:

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

强制转换是在明确表示你比编译器更清楚这实际上是什么。你可能想使用asis关键字?


在我的答案中加入香蕉让我浪费了宝贵的时间,而你比我更快。+1 分来自我。 - Paolo Tedesco
@PaoloTedesco 我喜欢你的香蕉示例。 - McKay

8
重点是,p 可以是一个 Question,因为 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
    }
}

6
当你进行显式转换时,你在告诉编译器“我知道你不知道的东西”。
实质上,你正在覆盖编译器的正常逻辑 - p可能是一个Question(因此,编译器会编译),但你告诉编译器你知道它是什么(即使它不是,因此运行时异常)。

2

1) 你的假设是错误的。某人总是可以为问题(Question)实现一个显式转换运算符,以从帖子(Post)进行转换:

public class Question`
{
    // some class implementation

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

2) 显式转换是告诉编译器你比它更懂的方式。如果你不确定一个强制类型转换是否成功,又不想出现运行时异常,就使用 isas 运算符。


你不会收到“不允许从基类进行用户定义的转换”吗? - Black Light
问题的提问者已经说明了类的内容(为空),因此这个答案是无效的。他甚至没有展示它们的任何部分,所以,“严格”讲,这个答案并不适用。 - Meligy
@MohamedMeligy - OP可能展示了他的实现...但编译器并不关心。仍然有可能存在显式转换操作,而编译器不会检查。 - Justin Niessner

1
编译器将p视为变量,因此不会尝试跟踪其值。如果它这样做了,分析整个应用程序需要很长时间。一些静态分析工具(如FxCop)确实会这样做。
编译器看到一个Post,但它没有跟踪赋值,并且知道可能:
Post p = new Question();

所以,它正常地通过了。

你知道你不能这样做:

Question q = p;

区别在于这个版本中,您试图告诉编译器使用它所知道的来验证它,而它知道Post不一定是一个Question

在原始版本中,您告诉编译器“我知道它是这样的,并且我将明确设置它,离开我的路,如果我所知道的是错误的,我会抛出异常”,因此,它听从您的指示并离开了您的路!


0
你的假设是正确的:它会编译通过,但在运行时会失败。
在你的小例子中,很明显强制转换会失败,但编译器无法知道这一点。由于PostQuestion的超类型,你可以将一个Question赋值给p,并且由于你进行了强制转换,你承担了一定的责任使得编译器失去了预警能力。如果你试图将一个string或者其他不属于相同继承分支的对象赋值给p,编译器应该会发出警告。相反地,你总是可以尝试将object强制转换为任何类型。
然而,如果编译器对你的具体例子进行投诉,意味着将不允许任何强制转换操作。

0

哇,Jeremy,我最近也遇到了这个问题!所以我写了这个方便的扩展方法,可以映射共享一些相同属性的两个模型。我的意图是在类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;
    }
}

顺便提一下,如果这两个对象存在于同一个程序集中,它可能只能正常工作。

其中很多代码来自于这里:使用反射c#映射业务对象和实体对象


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接