Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Will reassigning datasource cause a memory leak if listeners are not removed first?

I have two grids on my form. Whenever a row gets focus in grid1, its associated children rows are fetched from the database via ADO.NET and grid2's DataSource is reassigned as follows:

     //focused row changed handler
     DataTable Children = FetchChildren(parentid);
     grid2.DataSource = Children.DefaultView;
     Children.RowDeleted += (sndr, evt) =>
      {
        //
      };

ASIDE: grid1 contains many rows so I don't want to fetch the children rows for all of the parent rows in one (time-consuming) query and then filter the large data set of children rows client-side.

What happens to such anonymous eventlisteners when the local Children variable and the datasource of grid2 are reassigned many times during the user's working with the form? Does not explicitly removing the handlers cause a memory leak?

like image 989
Tim Avatar asked Nov 10 '22 00:11

Tim


1 Answers

No, in the code you show there will not be a memory leak assuming you use that term to indicate that objects don't get garbage collected. The delegate for the anonymous eventhandler is a reference that gets garbage collected a long with the rest of the objects created by the DataTable once it goes out of scope.

I've created the following test-rig to simulate what your code is doing:

static object DataSource;

static void Main(string[] args)
{
    Test1();
    // clean up
    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
    GC.WaitForPendingFinalizers();
    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
}

static void Test1()
{
    for (var i = 0; i < 1000; i++)
    {
        var dt = FetchChildren(i);
        DataSource = dt.DefaultView;
        dt.RowDeleted += (s, e) =>
        {
            var table = (DataTable)s;
            Trace.WriteLine(String.Format("{0}:{1}:{2}", e.Action, e.Row.RowState, table));
        };
        // do work
        var dv = (DataView)DataSource;
        dv.Delete(5);
    }
    DataSource = null;
}

// create a useful datatable
static DataTable FetchChildren(int parent)
{
    var dt = new DataTable();
    dt.Columns.Add("key", typeof(int));
    dt.Columns.Add("guid", typeof(string));

    for(var i=0; i<10; i++)
    {
        var row = dt.NewRow();
        row[0] = parent;
        row[1] = Guid.NewGuid().ToString("N");
        dt.Rows.Add(row);
    }

    return dt;
}

When I run this with the Performance Explorer in Visual Studio in Instrumentaion mode and with the Collect .Net Object lifetime information enabled this is my result:

Object lifetime

As you can see all DataTable instances are back at 0 when profiling ends and that goes for all types that are instantiated in that test.

If you keep references around however you can end up with 1000 instances at the end of the method as shown in this testcase:

static void Test2()
{
    for (var i = 0; i < 1000; i++)
    {
        var dt = FetchChildren(i);
        var local = DataSource; // our previous DataTable
        dt.RowDeleted += (s, e) =>
        {
            var table = (DataTable)s;
            Trace.WriteLine(String.Format("{0}:{1}:{2}", e.Action, e.Row.RowState, local)); // use it here
        };
        DataSource = dt.DefaultView;
        // do work
        var dv = (DataView)DataSource;
        dv.Delete(5);
    }
    //DataSource = null; // don't dereference
}

So as long as you don't keep references around to the instance you used previously you should be fine.

like image 88
rene Avatar answered Nov 14 '22 21:11

rene