删除无法访问的代码总是安全吗?

时间:2014-09-24 15:33:57

标签: c#

假设我有一个工具可以自动删除编译器检测到的无法访问的C#代码。是否存在这样的操作会让我陷入困境的情况?请分享有趣的案例。

5 个答案:

答案 0 :(得分:13)

这是一个有趣的例子。考虑这样的函数:

    public static IEnumerable<int> Fun()
    {
        if (false)
        {
            yield return 0;
        }            
    }

yield的行被检测为无法访问。但是,删除它将使程序无法编译。函数中包含的yield为编译器提供重新组织函数的信息,以便不执行任何操作只返回空集合。移除yield行后,它看起来就像普通函数一样,在需要时没有返回。

与评论一样,这个例子是设计的,但是false代替yield,我们可以从其他生成的项目等中获得一个常量值(即这样的代码看起来不会像这样显而易见情况)。

修改: 请注意,async/await构造实际上与{{1}}非常相似。然而,后者的语言创造者采用了一种不同的(IMO更好)方法来阻止这种情况。迭代器块可以用相同的方式定义(在函数签名中使用一些关键字而不是从函数体中检测它)。

答案 1 :(得分:3)

由于其他答案中提到的原因,我不会自动 ,但我会在这里说,我强烈倾向于删除未使用的代码而不是保留它。毕竟,跟踪过时的代码是源代码控制的目的。

答案 2 :(得分:3)

这个例子是设计的,但这会被标记为删除(默认DEBUG设置)并在删除时产生不同的行为。

public class Baz { }

public class Foo
{
    public void Bar()
    {
        if (false)
        {
            // Will be flagged as unreachable code
            var baz = new Baz();
        }

        var true_in_debug_false_in_release =
            GetType()
            .GetMethod("Bar")
            .GetMethodBody()
            .LocalVariables
            .Any(x => x.LocalType == typeof(Baz));

        Console.WriteLine(true_in_debug_false_in_release);
    }
}

Release模式下(使用默认设置),“无法访问的代码”将被优化掉,并产生与在if模式下删除DEBUG块时相同的结果。

与使用yield的示例不同,无论是否删除了无法访问的代码,此代码都会编译。

答案 3 :(得分:3)

Dan Bryant's answer之后,这里有一个程序示例,其行为将被一个比C#编译器更聪明的工具更改,以查找和删除无法访问的代码:

using System;

class Program
{
    static bool tru = true;

    static void Main(string[] args)
    {
        var x = new Destructive();
        while (tru)
        {
            GC.Collect(2);
        }
        GC.KeepAlive(x); // unreachable yet has an effect on program output
    }
}

class Destructive
{
    ~Destructive()
    {
        Console.WriteLine("Blah");
    }
}

C#编译器并没有非常努力地证明GC.KeepAlive无法访问,所以在这种情况下它并没有消除它。结果,程序永远循环而不打印任何东西。

如果某个工具证明它实际上无法访问(在此示例中相当简单)并将其删除,则程序行为会发生变化。它将立即打印“Blah”,然后永远循环。所以它已成为一个不同的程序。如果你有疑虑,试试吧;只是注释掉那条无法到达的行并看到行为改变。

如果GC.KeepAlive出于某种原因,那么这种改变实际上是不安全的,并且会使程序在某些时候行为不端(可能只是崩溃)。

答案 4 :(得分:0)

一个罕见的边界情况是无法访问的代码包含GC.KeepAlive调用。这很少出现(因为它与非托管/托管互操作的特定hacky用例有关),但是如果你与需要这种非托管代码的非托管代码进行互操作,如果你不幸有GC触发器,那么删除它可能会导致间歇性失败在错误的时刻。


更新:

我已经对此进行了测试,我可以确认Servy是正确的; GC.KeepAlive调用没有生效,因为编译器证明它永远不会实际使用该引用。这不是因为该方法永远不会执行(该方法不必因为它影响GC行为而执行它),而是因为当可证明无法访问时,编译器会忽略GC.KeepAlive。

我正在离开这个答案,因为它对于反案例仍然很有意思。如果您修改代码使其无法访问,这是一种破坏程序的机制,但不要移动GC.KeepAlive以确保它仍然使引用保持活动状态。