当您只有该列表的 "WhereEnumerableIterator" 时,是否有可能获得列表的原始类型?

Is it possible to get the original type of a list when all you have is a "WhereEnumerableIterator" of that list?

这可能很简单,但在过去的几个小时里却让我们有些头疼。

长话短说:我们需要修复内存泄漏,解决方法是 return 原始列表,如果在已经存在的列表上调用 ToBindingList,则不创建新的 MyBindingList 实例类型 MyBindingList

MyBindingList继承System.ComponentModel.BindingList

您将如何编写允许此测试通过的扩展方法 ToBindingList()?

[TestMethod]
public void FooBar()
{
    var SUT = new MyBindingList<FooBar>
    {
        new FooBar{Name = "AAA"},
        new FooBar{Name = "BBB"},
    };

    var filteredList = SUT.Where(x => x.Name == "AAA").ToBindingList();

    Assert.AreEqual(1, filteredList.Count);
    Assert.AreEqual(true, ReferenceEquals(filteredList, SUT));            
}

private class FooBar
{
    public string Name { get; set; }
}

MyBindingList 的构造函数是这样的

public class MyBindingList<T> : BindingList<T>, IEntityChanged
{
   public MyBindingList(IList<T> list) : base(list) { }

//...
}

我们遇到的问题是扩展方法对迭代器(Where 子句)进行操作,因此我们无法比较两个列表的类型信息。我们写了下面的扩展方法,然后变得更聪明了——然后卡住了:

public static MyBindingList<T> ToBindingList<T>(this IEnumerable<T> container)
{            
    var returnVal = container as MyBindingList<T>;
    if (returnVal != null)
        return returnVal;                

    return new MyBindingList<T>(container.ToList());
}

任何人都可以帮助我们找到可行的解决方案或解释为什么编译器永远不允许我们做这样的事情吗?

提前致谢

查看the source code,你可以使用反射来做到这一点:

var data = new List<int>();
var iterator = data.Where(x => 1 == 1);
var type = iterator.GetType();
var sourceField = type.GetField("source", System.Reflection.BindingFlags.NonPublic | 
    System.Reflection.BindingFlags.Instance);    
Console.WriteLine(sourceField.FieldType);

打印:

System.Collections.Generic.List`1[System.Int32]

您可以在 this fiddle 上进行测试。

所以你可以这样做来获取值:

public static List<T> GetOriginalList<T>(this IEnumerable<T> whereSource)
{
    var type = whereSource.GetType();
    var sourceField = type.GetField("source", 
        System.Reflection.BindingFlags.NonPublic |
        System.Reflection.BindingFlags.Instance |
        System.Reflection.BindingFlags.GetField);

    return sourceField as List<T>;
}

public static MyBindingList<T> ToBindingList<T>(this IEnumerable<T> container)
{            
    var returnVal = container as MyBindingList<T>;
    if (returnVal != null)
        return returnVal;                

    return new MyBindingList<T>(container.GetOriginalList<T>());
}

BindingList<T>(以及最近的 ObservableCollection<T>)并不是为了这个目的。他们的意思是包装由您的 ViewModel 或任何底层提供的集合的 'final' 结果。如果您需要过滤列表,您基本上有两个选择:*

  • Return BindingList<T> 的新实例(包装简单集合而不是另一个绑定列表)并重新绑定您的视图。
  • 通过从绑定列表中删除项目来处理 'filtering' 并只允许视图在处理列表更改通知时进行相应更新(毕竟这就是人们使用可观察集合的原因,例如 BindingList<T>).

*备注:如果你的View使用WinForms技术,你可以在表单中使用BindingSource类型(它的DataSource可以是你的绑定列表),它也实现了 IBindingList。它有一个 Filter 属性,这是一个字符串,可以接受花哨的表达式,但实际上我不会在实践中使用这个 属性。

绝对有可能,但有点老套。使用反射检查该类型是否为父级为 System.Linq.Enumerable 的嵌套类型。如果是,则使用反射获取私有 'source' 实例字段的值。那是Where的原始来源。这应该适用于所有 Enumerable 方法,但我建议您循环执行此操作,直到到达根源以支持更复杂的查询。我还建议在静态位置缓存反射结果。不过,公平警告 - 不能保证 'source' 将在即将发布的版本中继续成为私有字段的名称。依赖它意味着您需要为您打算 运行 它的每个 .NET Framework 版本重新测试它。它可能有一天会停止工作。