Main
我偶然发现了这样一种情况,即垃圾收集似乎在作为单元测试编写的相同代码与以控制台应用程序的方法编写的代码之间表现不同。我想知道这种差异背后的原因。
在这种情况下,我和一位同事就注册事件处理程序对垃圾收集的影响存在分歧。我认为演示会比简单地向他发送一个高度评价的 SO 答案的链接更好地接受。因此,我写了一个简单的演示作为单元测试。
我的单元测试表明事情按我说的那样工作。但是,我的同事编写了一个控制台应用程序,显示事情按他的方式工作,这意味着 GC 没有像我预期的那样在Main
方法中的本地对象上发生。Main
只需将代码从我的测试移到控制台应用程序项目的方法中,我就能够重现他看到的行为。
我想知道的是,为什么在Main
控制台应用程序的方法中运行时,GC 似乎没有按预期收集对象。通过提取方法,使调用GC.Collect
和超出范围的对象发生在不同的方法中,恢复了预期的行为。
这些是我用来定义我的测试的对象。只有一个带有事件的对象和一个为事件处理程序提供合适方法的对象。两者都有终结器设置一个全局变量,以便您可以知道它们何时被收集。
private static string Log;
public const string EventedObjectDisposed = "EventedObject disposed";
public const string HandlingObjectDisposed = "HandlingObject disposed";
private class EventedObject
{
public event Action DoIt;
~EventedObject()
{
Log = EventedObjectDisposed;
}
protected virtual void OnDoIt()
{
Action handler = DoIt;
if (handler != null) handler();
}
}
private class HandlingObject
{
~HandlingObject()
{
Log = HandlingObjectDisposed;
}
public void Yeah()
{
}
}
这是我的测试(NUnit),它通过了:
[Test]
public void TestReference()
{
{
HandlingObject subscriber = new HandlingObject();
{
{
EventedObject publisher = new EventedObject();
publisher.DoIt += subscriber.Yeah;
}
GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();
Thread.MemoryBarrier();
Assert.That(Log, Is.EqualTo(EventedObjectDisposed));
}
//Assertion needed for foo reference, else optimization causes it to already be collected.
Assert.IsNotNull(subscriber);
}
GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();
Thread.MemoryBarrier();
Assert.That(Log, Is.EqualTo(HandlingObjectDisposed));
}
我将上面的主体粘贴到Main
新控制台应用程序的方法中,并将调用转换Assert
为Trace.Assert
调用。两个相等断言都失败然后失败。如果需要,生成的 Main 方法的代码在这里。
我确实认识到应该将 GC 何时发生视为非确定性的,并且通常应用程序不应该关心它何时发生。在所有情况下,代码都是在发布模式下编译并针对 .NET 4.5。
编辑:我尝试过的其他事情
- 制作测试方法
static
,因为 NUnit 支持它;测试仍然有效。 - 我还尝试将整个 Main 方法提取到程序上的实例方法中并调用它。两个断言仍然失败。
- 归因
Main
于[STAThread]
或[MTAThread]
以防万一这有所作为。两个断言仍然失败。 - 基于@Moo-Juice 的建议:
- 我将 NUnit 引用到控制台应用程序以便我可以使用 NUnit 断言,但它们失败了。
- 我尝试对测试、测试的类、
Main
方法和包含Main
静态方法的类的可见性进行各种更改。没变。 - 我尝试将测试类设为静态,并将包含该
Main
方法的类设为静态。没变。