Лучший способ конвертировать IEnumerable <T> в пользовательский тип
У меня есть пользовательский тип коллекции, который определяется как таковой:
public abstract class RigCollectionBase<T> : Collection<T>, IEnumerable<T>, INotifyPropertyChanged, IBindingList, ICancelAddNew where T : BusinessObjectBase, new()
Примечание: это базовый класс, есть 20 или около того дочерних классов, которые реализованы так:
public class MyCollection : RigCollectionBase<MyObject>
В нашем коде мы используем много Linq, и, как вы, вероятно, знаете, функции Linq возвращают IEnumerable<T>
. Я ищу, это простой и простой способ вернуться к MyCollection
из IEumberable<MyObject>
. Кастинг не разрешен, я получаю исключение "Can not cast from..."
Вот ответ, который я придумал, и он действительно работает, но он кажется неудобным и... сложным. Может быть, это не так, но я решил, что я получу это там, чтобы увидеть, есть ли лучший способ.
public static class Extension
{
/// <summary>
/// Turn your IEnumerable into a RigCollection
/// </summary>
/// <typeparam name="T">The Collection type</typeparam>
/// <typeparam name="U">The Type of the object in the collection</typeparam>
/// <param name="col"></param>
/// <returns></returns>
public static T MakeRigCollection<T, U> (this IEnumerable<U> col) where T : RigCollectionBase<U>, new() where U : BusinessObjectBase, new()
{
T retCol = new T();
foreach (U myObj in col)
retCol.Add(myObj);
return retCol;
}
}
То, что я действительно ищу, я думаю, это. Есть ли способ реализовать базовый класс, чтобы я мог использовать простой прилив, чтобы перейти от IEnumerable в MyCollection...
var LinqResult = oldCol.Where(a=> someCondition);
MyCollection newCol = (MyCollection)LinqResult;
Нет, приведенный выше код не работает, и я на самом деле не на 100% уверен, почему это... но это не так. Он просто чувствует, что есть очень очевидный шаг, который я не вижу....
Ответы
Ответ 1
Ваш метод MakeRigCollection
- это в основном правильный способ сделать это. Вот вариант, который несколько более подробный для использования, но гораздо более простой в реализации:
TCollection MakeRigCollectionSimple<TCollection, TItem>(
this IEnumerable<TItem> items, TCollection collection)
where TCollection : ICollection<TItem>
{
foreach (var myObj in items)
collection.Add(myObj);
return collection;
}
Надеюсь, я понял. Вы используете его следующим образом:
MakeRigCollectionSimple(items, new MyCollection());
или
items.MakeRigCollectionSimple(new MyCollection());
Теперь у вас есть второй аргумент для заполнения, но взамен мы смогли избавиться от всех сумасшедших дженериков. Остались просто простые дженерики. И введите вывод вывода полностью. Кроме того, это будет работать для всех типов коллекций, а не только для ваших RigCollections.
Ответ 2
Пока ваша коллекция реализует IEnumerable<T>
, вы не можете сделать простой IEnumerable<T>
для своей конкретной коллекции из-за того, как работает наследование.
Вот почему существуют встроенные методы расширения LINQ, такие как IEnumerable<T>.ToList()
, которые делают именно то, что вы написали.
Единственное отличие состоит в том, что List<T>
предоставляет публичный конструктор, который принимает IEnumerable<T>
как параметр.
Ответ 3
Добавьте конструктор в MyCollection
, который принимает IEnumerable<T>
, и выполните:
var newCol = new MyCollection(oldCol.Where(a=> someCondition));
Ответ 4
Вы не можете отличать, потому что методы LINQ не меняют вашу оригинальную коллекцию. Они возвращают новые. Новые - это не экземпляры вашей коллекции, а коллекции LINQ, в зависимости от того, какой метод вы использовали. Причиной этого является отложенный характер методов LINQ.
У вас есть несколько возможностей:
- Вы можете создать явный или неявный оператор-подборщик в своем классе, но вам нужно будет реализовать его в каждом из ваших дочерних классов.
- Вы можете создать конструктор, который принимает
IEnumerable<T>
и непосредственно инициализирует вашу коллекцию из него - аналогично конструктору на List<T>
.
Ответ 5
Это немного сложно, но в целом вы можете себе представить, что LINQ перечисляет через вашу коллекцию (независимо от типа, просто важно, чтобы его можно было преобразовать в IQueryable
) и добавляет все соответствующие ссылки на объекты в новый список. Это зависит от IQueryProvider
, который используется для сбора результатов запроса. Таким образом, наиболее очевидным вариантом является создать пользовательский IQueryProvider
. Каждый, кто пробовал это, знает, сколько боли это может быть...
Однако IQueryProvider
обычно возвращает IEnumerable
, что приводит к двум параметрам:
- Используйте метод расширения LINQ
ToList
, чтобы создать System.Collections.Generic.List
и продолжайте использовать простые списки в качестве контейнеров или
- Добавить конструктор, принимающий
IEnumerable
.
Оба способа гораздо более похожи, чем вы думаете, потому что ToList
реализовано так:
public static List<T> ToList<T>(this IEnumerable<T> enumerable)
{
return new List<T>(enumerable);
}
Поэтому он просто делегирует всю тяжелую работу соответствующего конструктора List<T>
.
Большинство коллекций поддерживают конструкцию с помощью параметров IEnumerable
. Я действительно не знаю, почему System.Collections.ObjectModel.Collection<T>
не поддерживает IEnumerable
, но IList
. Однако это дает вам возможность реализовать базовый класс коллекции с двумя дополнительными конструкторами, чтобы упростить результаты ваших запросов:
MyRigCollectionBase(IEnumerable<T> enumerable)
: this (enumerable.ToList())
{
// Delegates to constructor below
}
MyRigCollectionBase(IList<T> list)
: base (list)
{
// Delegates to constructor of Collection<T>.
}
Это действительно не решает вашу перегрузку, но дает вам возможность создавать собственные коллекции из запросов:
var coll = new MyCollection(oldColl.Where(x => x.AddToList));
И это дает клиентам возможность использовать разные способы управления результатами своих запросов.
Также это позволяет вам создавать пользовательские расширения: 1
public class MyCollection : MyRigCollectionBase<MyObject>
{
public static ToMyCollection(this IEnumerable<MyObject> enumerable)
{
return new MyCollection(enumerable); // Calls our previously declared constructor.
}
}
Теперь вы можете запросить вот так:
var coll = oldColl.Where(x => x.AddToList).ToMyCollection();
Каждая специализация вашей коллекции может определять ее собственное расширение в пределах ее декларации. Каждый запрос, который возвращает IEnumerable<MyObject>
, сможет преобразовать результат в MyCollection
.
1 Я не уверен на 100%, если this IEnumerable<MyObject>
работает как параметр, а расширение можно вызвать для запроса, возвращающего IEnumerable<MyObject>
. Некоторое подтверждение будет приятным!
Ответ 6
Вы определенно не можете просто использовать IEnumerable<T>
для своего настраиваемого типа, поскольку компилятор никогда не узнает, как это сделать. Если вы не скажете, как использовать перегрузку оператора преобразования.