Лучший способ конвертировать 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> для своего настраиваемого типа, поскольку компилятор никогда не узнает, как это сделать. Если вы не скажете, как использовать перегрузку оператора преобразования.