C# 在析构函数运行后调用方法

C# 在析构函数运行后调用方法,c#,.net,optimization,compilation,clr,C#,.net,Optimization,Compilation,Clr,我已经读过了,但是我忘了,我在哪里看到了一个例子。看起来是这样的 public class Program { private static void Main() { new SomeClass(10).Foo(); } } public class SomeClass { public int I; public SomeClass(int input) { I = input; Consol

我已经读过了,但是我忘了,我在哪里看到了一个例子。看起来是这样的

public class Program
{
    private static void Main()
    {
        new SomeClass(10).Foo();
    }
}

public class SomeClass
{
    public int I;

    public SomeClass(int input)
    {
        I = input;
        Console.WriteLine("I = {0}", I);
    }

    ~SomeClass()
    {
        Console.WriteLine("deleted");
    }

    public void Foo()
    {
        Thread.Sleep(2000);
        Console.WriteLine("Foo");
    }
}
因此,输出应为:

I = 10
deleted
Foo
为什么??由于优化器。它看到该方法不使用任何字段,因此可以在调用该方法之前销毁对象。那它为什么不这样做呢

如果我找到了,我会贴一个例子


所以我找到了源头Pro.NET表现:萨沙·戈尔施泰因、迪玛·祖尔巴列夫、伊多·弗拉托

另一个问题与终结的异步性质有关 它发生在专用线程中。终结器可能会尝试 获取由应用程序代码持有的锁,然后 应用程序可能正在通过调用 GC.WaitForPendingFinalizers()。解决这个问题的唯一办法是 在超时情况下获取锁,如果无法获取,则正常失败 获得。还有一种情况是由垃圾收集器的 渴望尽快恢复记忆。考虑一下 下面的代码表示一个文件类的幼稚实现 使用关闭文件句柄的终结器:

class File3
{
    Handle handle;
    public File3(string filename)
    {
        handle = new Handle(filename);
    }
    public byte[] Read(int bytes)
    {
        return Util.InternalRead(handle, bytes);
    }
    ~File3()
    {
        handle.Close();
    }
}

class Program
{
    static void Main()
    {
        File3 file = new File3("File.txt");
        byte[] data = file.Read(100);
        Console.WriteLine(Encoding.ASCII.GetString(data));
    }
}
这段无辜的代码可能会以非常恶劣的方式被破坏。阅读 方法可能需要很长时间才能完成,并且它只使用句柄 包含在对象中,而不是对象本身。规则 确定局部变量何时被视为活动根变量 客户持有的局部变量在 对Read的调用已被调度。因此,对象是 被认为有资格进行垃圾收集,其终结器可能 在Read方法返回之前执行!如果发生这种情况,我们可能会 在使用手柄时或使用前关闭手柄


但是我不能复制这种行为

不管怎样,我找到了复制它的方法,我应该更仔细地阅读:):

所以在这种情况下,将在Foo方法之前调用析构函数。
问题是因为您在Foo中使用了线程。您告诉代码等待1秒,但不告诉它在执行其他所有操作之前等待第二秒。因此,原始线程在Foo完成之前执行析构函数

编写Foo的更好方法如下:

public void Foo()
{
  var mre = new ManualResetEvent(false);
    mre.WaitOne(1000);

  Console.WriteLine("Foo");
}
使用ManualResetEvent将强制代码完全暂停,直到达到超时。之后,代码将继续

public void Foo()
{
    Thread.Sleep(1000);
    Console.WriteLine("Foo");
}
不使用类的任何实例成员的方法应该声明为静态的。它有几个优点,对代码的读者非常有帮助。它明确指出,方法不会改变对象状态

另一个好处是,您现在可以理解为什么在对象完成后看到方法运行没有差异。GC只是没有任何理由让它保持活动状态,当Foo()开始执行时,没有对对象的引用。因此,毫无困难地收集并最终确定


您将在中找到更多关于jitter如何向垃圾收集器报告对象引用的背景信息。

Code在deleted之前仍然打印Foo,因为Main方法仍在执行,所以SomeClass实例从未超出范围,因此不符合GC收集的条件。也许您在中编译调试?您应该将自己的回复标记为答案。我认为为了完整性,你需要提到的是,如果你有问题,使用“解决”了问题。不是RY,只是一篇关于一些鲜为人知的优化的帖子。我只能在两天内标出我的答案。@RohitVats对不起,我从未见过这样的文章。重要的是要记住,优化是一个实现细节,在编译器版本之间可能会发生变化。通过C#book(第510页,第四部分:核心设施,第21章:托管堆和垃圾收集)和各种互联网文章(如:我可以使用字段,并且由于Clr标准,输出应该是相同的),Clr中已经知道并描述了这种精确的优化。简单的WriteLine只是一个例子,它当然不一样了。由于“CLR标准”,方法永远无法访问最终确定对象的字段。很明显,这将是灾难性的。但仍有人写道,
确定局部变量何时被视为活动根的规则规定,在调用Read之后,客户端持有的局部变量不再相关。因此,该对象被认为有资格进行垃圾收集,并且它的终结器可能会在Read方法返回之前执行!如果发生这种情况,我们可能会在使用句柄时关闭它,或者在使用句柄之前关闭它。
那么这是错误的吗?不,这不是错误的。这正是您所看到的,当Foo()正在执行时,对象是gc-ed的。
newsomeclass()
引用不再相关。你不把它存储在局部变量中,但这没关系。我试图用一个字段来复制它,但没有成功。字段现在是
Stream
,方法调用
Stream.Read
,所以现在对象在
Foo方法完成后被删除
,不管它工作多长时间。
public void Foo()
{
    Thread.Sleep(1000);
    Console.WriteLine("Foo");
}