1

I have a main method that has this code:

List<MyType> myList = openDialog();

The call to the openDialog that open a dialog that returns a list with selected items, the dialog is to select items.

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

    return myViewModel.Result;        
}

myViewModel.Result is a collection that has the selectedItems of the a datagrid in the view.

My question is, how I am returning the Result property of the ViewModel, I am not sure if myViewModel will be recollected by the garbage collector or not, because it still has a reference to it.

To avoid this I am doing this:

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

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

In the return, I am creating a new list to avoid to reference to the Result property and ensure that the myViewModel object is recollected, but I would like to know if there is a way to avoid to create a new list.

rene
  • 41,474
  • 78
  • 114
  • 152
Álvaro García
  • 18,114
  • 30
  • 102
  • 193
  • 1
    Do you have reason to believe it doesn't get GC-ed? Does the memory consumption keep going up? – rene May 27 '18 at 07:45
  • Well, my problem is that I don't know how to test if the object is recollected or not. I am using VS community, if it has the opton to debug and check this, I don't know how. Or perhaps it is possible only in the professional and avobe versions. Thanks. – Álvaro García May 27 '18 at 08:06
  • I would start with watching the memory performance counters of the process (those come with your Windows OS). And if your suspicion still holds, switch to [perfview](https://blogs.msdn.microsoft.com/dotnet/2012/10/09/improving-your-apps-performance-with-perfview/) – rene May 27 '18 at 08:12
  • It would be awesome if you could provide a [mcve] (since it is hard to give good advice without seeing exactly what `MyViewModel ` and `Result` are, for example). Also, http://www.levibotelho.com/development/how-does-the-garbage-collector-work/ may be worth a read. – mjwills May 27 '18 at 09:19
  • Take a memory profiler and see, don't guess. – Ed Pavlov Jun 06 '18 at 15:02

3 Answers3

2

You haven't posted the implementation of MyViewModel but the the List<MyType> returned from the view model won't prevent the view model from being garbage collected. You can confirm this yourself using a 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() };
}

isViewModelAlive is false and result.Count is 2 when running the above sample code, i.e. the view model has been collected but the List<MyType> remains in memory.

Note that WeakReference.IsAlive will return true when you hold a strong reference to the method that performs the GC test:

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;
}

But myViewModel will still be eligible for garbage collection once the method has returned as it has no GC roots.

So there is no need to create another List<MyType> here.

mm8
  • 163,881
  • 10
  • 57
  • 88
1

This question is a little confused. However lets test it out.

Given

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;
   }
}

We can look at whats being collected and not

// 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));

Output

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

The fact is, just because you are returning a list from your class and its part of the class doesn't mean returning it will stop your viewmodal from being cleaned up. It "will" get collected in this situation and there are no memory leaks

Further reading

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

Memory in .NET - what goes where

The memory slot for a variable is stored on either the stack or the heap. It depends on the context in which it is declared:

  1. Each local variable (ie one declared in a method) is stored on the stack. That includes reference type variables - the variable itself is on the stack, but remember that the value of a reference type variable is only a reference (or null), not the object itself. Method parameters count as local variables too, but if they are declared with the ref modifier, they don't get their own slot, but share a slot with the variable used in the calling code. See my article on parameter passing for more details.
  2. Instance variables for a reference type are always on the heap. That's where the object itself "lives".
  3. Instance variables for a value type are stored in the same context as the variable that declares the value type. The memory slot for the instance effectively contains the slots for each field within the instance. That means (given the previous two points) that a struct variable declared within a method will always be on the stack, whereas a struct variable which is an instance field of a class will be on the heap.
  4. Every static variable is stored on the heap, regardless of whether it's declared within a reference type or a value type. There is only one slot in total no matter how many instances are created. (There don't need to be any instances created for that one slot to exist though.) The details of exactly which heap the variables live on are complicated, but explained in detail in an MSDN article on the subject.

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

TheGeneral
  • 79,002
  • 9
  • 103
  • 141
0

Whether the object referenced by myViewModel variable can be collected depends on whether any other reachable object references it. In this case, assuming no one else has a reference to it, it depends on whether the object returned by the myViewModel.Result property has any reference (e.g. field, property, delegate) to it. If it doesn't, the copying of the collection isn't necessary. If it does, the copy seem to be an acceptable solution.

Unless the view model uses some limited resources (database connection, file handles, network connections and so on, in which case it should use IDisposable pattern), there's no reason to be overly anxious about its garbage collection.

However, to satisfy one's curiosity, you may test whether the object is GCed using the answer here.

grepfruit
  • 181
  • 9