Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 控制台应用程序和单元测试方法之间的不同垃圾收集行为_C#_.net_Garbage Collection - Fatal编程技术网

C# 控制台应用程序和单元测试方法之间的不同垃圾收集行为

C# 控制台应用程序和单元测试方法之间的不同垃圾收集行为,c#,.net,garbage-collection,C#,.net,Garbage Collection,我偶然发现了这样一种情况,即垃圾收集在作为单元测试运行的相同代码与在控制台应用程序的Main方法中编写的相同代码之间的行为似乎有所不同。我想知道这种差异背后的原因 在这种情况下,我和一位同事在注册事件处理程序对垃圾收集的影响上存在分歧。我认为一个示范会比简单地给他发一个链接到一个网站更容易被接受。因此,我编写了一个简单的演示作为单元测试 我的单元测试表明事情按照我说的那样运行。然而,我的同事编写了一个控制台应用程序,显示了它的工作方式,这意味着GC并没有像我预期的那样在Main方法中的本地对象上

我偶然发现了这样一种情况,即垃圾收集在作为单元测试运行的相同代码与在控制台应用程序的
Main
方法中编写的相同代码之间的行为似乎有所不同。我想知道这种差异背后的原因

在这种情况下,我和一位同事在注册事件处理程序对垃圾收集的影响上存在分歧。我认为一个示范会比简单地给他发一个链接到一个网站更容易被接受。因此,我编写了一个简单的演示作为单元测试

我的单元测试表明事情按照我说的那样运行。然而,我的同事编写了一个控制台应用程序,显示了它的工作方式,这意味着GC并没有像我预期的那样在
Main
方法中的本地对象上发生。通过将代码从我的测试移动到控制台应用程序项目的
Main
方法中,我能够重现他看到的行为

我想知道的是,为什么GC在控制台应用程序的
Main
方法中运行时,似乎没有像预期的那样收集对象。通过提取方法,使对
GC.Collect
的调用和超出范围的对象在不同的方法中发生,恢复了预期的行为

这些是我用来定义测试的对象。只有一个带有事件的对象和一个为事件处理程序提供合适方法的对象。两者都有终结器,设置了一个全局变量,这样您就可以知道何时收集了它们

私有静态字符串日志;
public const string EventedObjectDisposed=“EventedObjectDisposed”;
public const string HandlingObjectDisposed=“HandlingObjectDisposed”;
私有类事件对象
{
公共事件行动DoIt;
~EventedObject()
{
日志=EventObjectDisposed;
}
受保护的虚拟void OnDoIt()
{
动作处理程序=DoIt;
if(handler!=null)handler();
}
}
私有类处理对象
{
~HandlingObject()
{
Log=处理对象;
}
公共空间
{
}
}
这是我的测试(NUnit),它通过了:

[测试]
公共void TestReference()
{
{
HandlingObject订户=新HandlingObject();
{
{
EventedObject publisher=新的EventedObject();
publisher.DoIt+=subscriber.yes;
}
GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();
Thread.MemoryBarrier();
Assert.That(Log,Is.EqualTo(EventedObjectDisposed));
}
//foo引用需要断言,否则优化会导致它已被收集。
Assert.IsNotNull(订户);
}
GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();
Thread.MemoryBarrier();
Assert.That(Log,Is.EqualTo(HandlingObjectDisposed));
}
我将上面的主体粘贴到新控制台应用程序的
Main
方法中,并将
Assert
调用转换为
Trace.Assert
调用。两个相等断言都失败,然后失败。生成的Main方法的代码是if-you-want

我确实认识到GC发生的时间应该被视为非确定性的,并且通常应用程序不应该关心它确切发生的时间。 在所有情况下,代码都是以发布模式编译的,目标是.NET4.5

编辑:我尝试过的其他事情

  • 使测试方法
    静态
    ,因为NUnit支持该方法;测试仍然有效
  • 我还尝试将整个Main方法提取到程序上的实例方法中,并调用它。两项主张仍然失败
  • [STAThread]
    [MTAThread]
    在case中为
    Main
    添加属性会有所不同。两项主张仍然失败
  • 根据@Moo Juice的建议:
    • 我将NUnit引用到控制台应用程序,以便使用NUnit断言,但它们失败了
    • 我尝试对test、test的类、
      Main
      方法以及包含
      Main
      静态方法的类的可见性进行各种更改。没有变化
    • 我尝试将测试类设置为静态,将包含
      Main
      方法的类设置为静态。没有变化

如果将以下代码提取到一个单独的方法中,则测试更有可能按照您的预期进行。编辑:请注意,C语言规范的措辞并不要求通过此测试,即使您将代码提取到单独的方法中

        {
            EventedObject publisher = new EventedObject();
            publisher.DoIt += subscriber.Yeah;
        }
规范允许但不要求发布者在该块末尾立即符合GC的条件,因此您不应以假定可以在此处收集代码的方式编写代码

编辑:来自ECMA-334(C#语言规范)§10.9自动内存管理(emphasis mine)

如果除运行终结器外,任何可能的执行继续都无法访问对象的任何部分,则该对象将被视为不再使用,并有资格进行终结。[注意:实现可能会选择分析代码,以确定将来可以使用对对象的哪些引用。例如,如果范围内的局部变量是对对象的唯一现有引用,但该局部变量在从中的当前执行点开始的任何可能的执行延续中从未被引用在该过程中,实现可能(但不要求)将对象视为不再使用。结束注意]


问题不在于它是一个控制台应用程序-问题在于您可能正在通过Visual Studio运行它-附带了一个调试器!和/或您正在将console应用程序编译为调试版本

确保你是康比