Можно ли получить оригинальный тип списка, когда все, что у вас есть, это «WhereEnumerableIterator» этого списка? - PullRequest
0 голосов
/ 24 октября 2018

Это может быть просто, но, тем не менее, это вызвало у нас небольшую головную боль за последние несколько часов.

Короче говоря: нам нужно исправить утечку памяти, и способ сделать это - вернутьисходный список без создания нового экземпляра 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 голосов
/ 24 октября 2018

BindingList<T> (а в последнее время ObservableCollection<T>) просто не для этой цели.Они предназначались для переноса «окончательного» результата коллекции, предоставленной вашей ViewModel или каким-либо другим нижележащим слоем.Если вам нужно отфильтровать список, у вас есть два основных варианта: *

  • Вернуть новый экземпляр BindingList<T> (обертывание простых коллекций вместо другого списка привязок) и заново привязать ваше представление.
  • Обработайте «фильтрацию», удалив элементы из списка привязок, и просто разрешите представлению обновляться соответствующим образом, поскольку оно обрабатывает уведомления об изменении списка (в конце концов, именно поэтому используется наблюдаемая коллекция, такая как BindingList<T>).

* Примечание: Если в вашем View используется технология WinForms, вы можете использовать тип BindingSource в форме (его DataSource может быть вашим списком привязок),который также реализует IBindingList.У него есть свойство Filter, которое является строкой и может принимать необычные выражения, но на самом деле я бы не использовал это свойство на практике.

0 голосов
/ 24 октября 2018

Определенно возможно, но хаки.Используйте отражение, чтобы проверить, является ли тип вложенным типом, чьим родителем является System.Linq.Enumerable.Если это так, используйте отражение, чтобы получить значение частного поля экземпляра 'source'.Это первоначальный источник Где.Это должно работать для всех методов Enumerable, но я рекомендую делать это в цикле, пока вы не достигнете корневого источника для поддержки более сложных запросов.Я также рекомендую кэшировать результаты отражения в статическом месте.Честное предупреждение, хотя - нет никакой гарантии, что «source» останется именем частного поля в следующих выпусках.Чтобы полагаться на него, вам необходимо повторно протестировать его для каждого выпуска платформы .NET Framework, на котором вы собираетесь его запускать.Это может просто перестать работать однажды.

0 голосов
/ 24 октября 2018

Глядя на исходный код , вы можете сделать это, используя Reflection:

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]

Вы можете проверить это на эта скрипка .

Так что вы можете сделать что-то вроде этого, чтобы получить значение:

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