我的表格上有两个网格。当一行在grid1中获得焦点时,其关联的子行将通过ado.net从数据库中获取,grid2的数据源将重新分配如下:

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

旁白:grid1包含许多行,因此我不想在一个(耗时的)查询中获取所有父行的子行,然后在客户端筛选子行的大数据集。
当用户在使用窗体期间多次重新分配本地子变量和grid2的数据源时,这种匿名事件侦听器会发生什么情况?不显式删除处理程序会导致内存泄漏吗?

最佳答案

不,在您显示的代码中,如果使用该术语来指示对象未被垃圾收集,则不会出现内存泄漏。匿名eventhandler的委托是一个引用,一旦它超出作用域,就会用DataTable创建的其余对象进行长时间的垃圾收集。
我创建了以下测试设备来模拟您的代码正在执行的操作:

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

当我在visual studio的performance explorer中以instrumentaion模式运行此程序并启用collect.net对象生存期信息时,这是我的结果:
c# - 如果不先删除监听器,重新分配数据源是否会导致内存泄漏?-LMLPHP
正如您所看到的,当分析结束时,所有datatable实例都返回到0,这适用于在该测试中实例化的所有类型。
但是,如果保留引用,则可以在方法的末尾出现1000个实例,如本测试用例所示:
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
}

因此,只要不保留对以前使用的实例的引用,就应该没问题。

08-04 03:25
查看更多