为什么不';t C#编译器捕获InvalidCastException

本文关键字:编译器 InvalidCastException 为什么不 | 更新日期: 2023-09-27 18:14:53

可能重复:
编译时和运行时强制转换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. 如果我的假设是正确的,那么编译器为什么不警告这个错误呢

为什么不';t C#编译器捕获InvalidCastException

允许此转换有几个原因。

首先,正如人们在其他答案中所说,演员的意思是";我知道的比你多;我向你保证,这个转换会成功,如果我错了,抛出一个异常并破坏进程"。如果你对编译器撒谎,坏事就会发生;事实上,并不是在做出保证,因此程序正在崩溃。

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

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

其次,可以(Derived)(new Base()),其中Derived是一个实现类型Base的类型,并使其在运行时失败。(Base)(new Base())也有可能在运行时因无效的强制转换异常而失败!真实的事实!这些情况非常罕见,但它们可能的。

有关更多详细信息,请参阅我关于以下主题的文章:

  • C中链接的用户定义显式转换#
  • C#中链接的用户定义显式转换,第二部分
  • C#中链接的用户定义显式转换,第三部分

在某些情况下,Post可以强制转换为Question。通过执行强制转换,你告诉编译器,"我保证,这会起作用。如果不起作用,你可以抛出一个无效的强制转换异常。">

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

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

强制转换明确表示您比编译器更清楚这到底是什么。您可能想做一些类似于asis关键字的操作?

关键是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
    }
}

当您执行显式强制转换时,您告诉编译器"我知道一些您不知道的东西"。

从本质上讲,您正在覆盖编译器的正常逻辑-p可能是Question(因此,编译器将进行编译(,您正在告诉编译器您知道它是(即使它不是,因此运行时异常(。

1(你的假设是错误的。有人总是可以为Question实现一个显式转换运算符,以从Post转换:

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

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

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

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

Post p = new Question();

所以,它正常通过。

你知道你不能做:

Question q = p;

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

在最初的版本中,你告诉编译器"我知道它是,我会明确设置它,走开,如果我知道的是错误的,我会破例",所以,它会听你的,然后走开!

您的假设是正确的:它会编译,但在运行时会失败。

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

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

哇,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#映射业务对象和实体对象