为什么不能在编译时检查下击?

时间:2010-02-21 19:20:38

标签: c# .net

为什么编译器无法在编译时检测到 obj 引用 B 类型的对象,从而在我们尝试将其强制转换为时报告错误A

public class A { }
public class B { }

static void Main(string[] args)
{
   B b = new B();
   object obj = (object)b;
   A a = (A)obj; // exception

感谢名单

8 个答案:

答案 0 :(得分:14)

由于Halting problem。这实际上意味着您无法确定程序将遵循哪条执行路径(并且有一个数学证明)。例如,以下代码可能正确也可能不正确:

object o = SomeTest() ? (new A()) : (new B());
A a = (A)o;

如果SomeTest方法始终返回true,那么它是正确的。不幸的是,不可能做出决定。然而,该领域正在进行大量研究。即使它不能始终检查,但有些工具有时可以验证某些内容是否会成功,或者为您提供假设失败的执行路径示例。

这种技术的一个很好的例子是Code Contracts,它将成为Visual Studio 2010的一部分。我相信你可以用它们来证明你的下击是正确的。但是,没有明确的支持 - 尽管它会很有用!

答案 1 :(得分:7)

让我转过问题:如果编译器可以证明这一点,那么为什么我们需要演员表呢?演员的目的是告诉编译器“我比你更了解这个代码,并且我保证这个演员是有效的。我很确定这个事实,我愿意让你生成抛出异常的代码如果我错了。“编译器无法证明转换是有效的,因为转换是 for 场景,编译器无法证明它是有效的。

答案 2 :(得分:6)

您希望编译器遵循控制流程,并提前确定转换将导致异常?何必?使用真正的程序,控制流程将太复杂,无法解决这个问题。

答案 3 :(得分:6)

编译器当然可以实现在这样的微不足道的情况下工作的检查。但这样做不太可能帮助“真正的”代码,因为程序员很少编写这样明显错误的代码。

为了处理更复杂的情况,编译器必须执行更复杂的分析。这对编译器编写者来说很难做,而且机器运行速度也慢,而且仍然无法捕获每个可能的错误演员。而且,由于大多数代码都没有这种容易识别的错误,因此不清楚收益是否值得编写分析的成本。

更复杂的静态分析的两个缺点是错误消息和误报。首先,在代码中使用工具解释问题通常比使工具仅检查问题要困难一个数量级。其次,当检查问题从“坏事X肯定会发生”变为“可能发生的坏事”时,该工具更有可能标记在实践中从来不是问题的事情。

有一篇有趣的文章由一家公司撰写,销售静态分析工具,这些工具是从学术研究中分离出来的。他们发现的一件事是他们经常通过更复杂的分析减少销售量! A Few Billion Lines of Code Later: Using Static Analysis to Find Bugs in the Real World

答案 4 :(得分:2)

即使是静态分析工具也无法解决这个问题。如果您的代码使用反射怎么办?

void Test(string typeName)
{
    Type t = Type.GetType(typeName);
    object obj = Activator.CreateInstance(t);
    A a = (A)obj;
    // etc.
}

这会抛出异常吗?没有实际运行它,绝对没有办法知道答案。没有多少代码路径分析会解决依赖于某些特定参数的的错误。如果你必须运行代码来检测错误,那么这会使它成为运行时错误,而不是编译时。

这正是您需要测试代码的原因。编译器无法确保您的代码正确,只是它在语法上有效并遵循语法中的任何规则。

虽然这看起来像是一个人为的例子,但是从你的O / R映射器到你的DI框架,反射现在几乎无处不在。在现代应用程序中,实际上很常见的是在运行时之前不知道某个实例的类型,或者至少不知道特定的具体类型

答案 5 :(得分:1)

因为您在那里坐了好几天,而编译器尝试了代码中的每一条路径。

答案 6 :(得分:1)

正如其他人所提到的,一般问题是编译器必须追溯所有可能的执行路径以查看该变量的来源 - 然后确定转换是否有效。

想象一下,如果对象被传递给函数,然后向下传播它。编译器必须知道传入的对象的运行时类型。如果这是一个库,调用时甚至可能不存在调用代码。

答案 7 :(得分:1)

在像你这样的基本示例中,人们可能会认为编译器很容易智能地查找对特定对象的所有引用,然后查看它是否被非法转换。但请考虑这个反例:

public class A { } 
public class B { } 

static void Main(string[] args) 
{ 
   B b = new B(); 
   object obj = (object)b;
   // re-using the obj reference
   obj = new A();
   A a = (A)obj; // cast is now valid

有许多可能的方法可以重复使用并转换编译器编写者需要预见的特定基本引用。当obj引用在参数中传递给方法时,它会变得更加复杂。编译时检查变得不确定,使得编译时间可能更长,并且仍然不能保证它能够捕获所有无效的强制转换。