在这种情况下,gargabe收集器是否会重新收集此对象?

时间:2018-05-27 07:31:33

标签: c# .net wpf mvvm

我有一个包含此代码的main方法:

List<MyType> myList = openDialog();

调用openDialog,打开一个返回包含所选项目的列表的对话框,该对话框用于选择项目。

private List<MyType> openDialog()
{
    MyView myView = new MyView();
    MyViewModel myViewModel = new MyViewModel();
    myView.DataContext = myViewModel;
    myView.ShowDialog();

    return myViewModel.Result;        
}

myViewModel.Result是一个集合,其中包含视图中数据网格的selectedItems。

我的问题是,我如何返回ViewModel的Result属性,我不确定垃圾收集器是否会重新收集myViewModel,因为它仍然有对它的引用。

为了避免这种情况,我这样做:

private List<MyType> openDialog()
{
    MyView myView = new MyView();
    MyViewModel myViewModel = new MyViewModel();
    myView.DataContext = myViewModel;
    myView.ShowDialog();

    return new List<MyType>(myViewModel.Result);        
}

在返回时,我正在创建一个新列表以避免引用Result属性并确保重新收集myViewModel对象,但我想知道是否有办法避免创建新列表。< / p>

3 个答案:

答案 0 :(得分:2)

您还没有发布MyViewModel的实现,但是从视图模型返回的List<MyType>不会阻止视图模型被垃圾回收。您可以使用WeakReference

自行确认
private void Test(object sender, RoutedEventArgs e)
{
    Window myView = new Window();
    MyViewModel myViewModel = new MyViewModel();
    myView.DataContext = myViewModel;
    myView.ShowDialog();

    List<MyType> result = myViewModel.Result;

    WeakReference viewModelWeakReference = new WeakReference(myViewModel);
    myView.DataContext = null;
    myViewModel = null;

    GC.Collect();
    GC.WaitForPendingFinalizers();

    bool isViewModelAlive = viewModelWeakReference.IsAlive; //=false
}
...
public class MyViewModel
{
    public List<MyType> Result { get; } = new List<MyType>() { new MyType(), new MyType() };
}
运行上面的示例代码时,

isViewModelAlivefalseresult.Count2,即已收集视图模型,但List<MyType>仍保留在内存中

请注意,当您对执行GC测试的方法有强引用时,WeakReference.IsAlive将返回true

List<MyType> _result;
private void Button_Click(object sender, RoutedEventArgs e)
{
    _result = openDialog();
}

private List<MyType> openDialog()
{
    Window myView = new Window();
    MyViewModel myViewModel = new MyViewModel();
    myView.DataContext = myViewModel;
    myView.ShowDialog();

    List<MyType> result = myViewModel.Result;

    WeakReference viewModelWeakReference = new WeakReference(myViewModel);
    myView.DataContext = null;
    myViewModel = null;

    GC.Collect();
    GC.WaitForPendingFinalizers();

    bool isViewModelAlive = viewModelWeakReference.IsAlive;

    return result;
}

但是一旦方法返回,myViewModel仍然有资格进行垃圾收集,因为它没有GC根。

因此,无需在此处创建另一个List<MyType>

答案 1 :(得分:1)

这个问题有点困惑。但是让我们测试一下。

鉴于

public class MyGCCollectClass
{
   public List<Version> Garbage { get; set; }

   public List<int> MyInnocentList { get; set; }

   public List<int> Main()
   {
      Garbage = new List<Version>();
      for (var i = 0; i < 10000000; i++)
      {
         Garbage.Add(new Version());
      }

      MyInnocentList = new List<int>();
      return MyInnocentList;
   }
}

我们可以看看收集的是什么而不是

// Lets start from a collect
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Memory used before collection:       {0:N0}", GC.GetTotalMemory(false));

// create a class/viewmodel
var gcCollectClass = new MyGCCollectClass();

// create a world of garbage
// return back a list that is part of the class
var list = gcCollectClass.Main();

// lets see whats GC has
Console.WriteLine("Memory used: {0:N0}", GC.GetTotalMemory(true));

// make sure our garbage is still alive
Console.WriteLine(gcCollectClass.Garbage.Count);

// Kill the original class
gcCollectClass = null;

// Force a collection
GC.Collect();
GC.WaitForPendingFinalizers();

// double check the list is still alive
Console.WriteLine(list.Count);

// Lets make sure we havent caused a memory leak
Console.WriteLine("Memory used after full collection:   {0:N0}", 
GC.GetTotalMemory(true));

<强>输出

Memory used before collection:       30,088
Memory used:   307,138,940
10000000
1
Memory used after full collection:   29,968

事实是,只是因为你从你的班级返回一个列表而且它的一部分并不意味着返回它会阻止你的视图模型被清除。在这种情况下“会”收集并且没有内存泄漏

进一步阅读

Why doesn't C# support the return of references?

Memory in .NET - what goes where

  

变量的内存插槽存储在堆栈或   堆。它取决于声明它的上下文:

     
      
  1. 每个局部变量(即在方法中声明的变量)都存储在堆栈中。这包括引用类型变量 - 变量本身   在堆栈上,但请记住引用类型变量的值   只是一个引用(或null),而不是对象本身。方法   参数也计为局部变量,但如果它们是声明的   ref修饰符,他们没有自己的插槽,但共享一个插槽   调用代码中使用的变量。请参阅我的参数文章   传递了更多细节。
  2.   
  3. 引用类型的实例变量始终在堆上。这就是物体本身“存在”的地方。
  4.   
  5. 值类型的实例变量存储在与声明值类型的变量相同的上下文中。内存槽为   实例有效地包含了每个字段的插槽   实例。这意味着(给出前两点)结构   在方法中声明的变量将始终在堆栈上,而   一个struct变量,它是一个类的实例字段   堆。
  6.   
  7. 每个静态变量都存储在堆上,无论它是在引用类型还是值类型中声明。只有   无论创建多少个实例,总共一个插槽​​。 (那里   不需要为该一个插槽创建任何实例   虽然。)变量所依赖的堆的详细信息是   复杂,但在MSDN文章中有详细解释   主题。
  8.   

https://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/

答案 2 :(得分:0)

是否可以收集myViewModel变量引用的对象取决于是否有任何其他可到达对象引用它。在这种情况下,假设没有其他人有引用它,它取决于myViewModel.Result属性返回的对象是否具有任何引用(例如字段,属性,委托)。如果没有,则不需要复制集合。如果是这样,副本似乎是一个可接受的解决方案。

除非视图模型使用一些有限的资源(数据库连接,文件句柄,网络连接等,在这种情况下它应该使用IDisposable模式),否则没有理由过于担心它的垃圾收集。

但是,为了满足一个人的好奇心,您可以测试该对象是否为GC using the answer here