如果您只有列表的“ WhereEnumerableIterator”,是否可以获取列表的原始类型?

时间:2018-10-24 13:57:36

标签: c# .net linq extension-methods

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

长话短说:我们需要解决内存泄漏问题,并且如果在已经属于MyBindingList类型的列表上调用ToBindingList,则返回原始列表而不创建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());
}

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

预先感谢

3 个答案:

答案 0 :(得分:0)

看看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>());
}

答案 1 :(得分:0)

BindingList<T>(最近是ObservableCollection<T>)并非仅用于此目的。它们的意思是包装由ViewModel或任何基础层提供的集合的“最终”结果。如果您需要过滤列表,则基本上有两个选择:*

  • 返回BindingList<T>的新实例(包装简单集合而不是另一个绑定列表)并重新绑定视图。
  • 通过从绑定列表中删除项目来处理“过滤”,并只允许视图在处理列表更改通知时进行相应更新(毕竟这就是为什么使用诸如BindingList<T>之类的可观察集合的原因)。

* 备注:如果您的View使用WinForms技术,则可以使用以下形式的BindingSource类型(其DataSource可以作为您的绑定列表),该形式也可以实现IBindingList。它有一个Filter属性,它是一个字符串,可以接受奇特的表达式,但实际上我不会在实践中使用此属性。

答案 2 :(得分:0)

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