Подтвердить что ты не робот

IList <T> и IReadOnlyList <T>

Моя проблема в том, что у меня есть метод, который может взять коллекцию как параметр, который

  • Имеет свойство Count
  • Имеет целочисленный индекс (get-only)

И я не знаю, какой тип должен иметь этот параметр. Я бы выбрал IList<T> до .NET 4.5, так как для этого не было другого интерфейса для индексируемых коллекций, а массивы реализуют его, что является большим плюсом.

Но .NET 4.5 представляет новый интерфейс IReadOnlyList<T>, и я хочу, чтобы мой метод тоже поддерживал это. Как я могу написать этот метод для поддержки как IList<T>, так и IReadOnlyList<T> без нарушения основных принципов, таких как DRY?

Можно ли преобразовать IList<T> в IReadOnlyList<T> как-то в перегрузке?
Каков путь сюда?

Изменить: ответ Даниэля дал мне некоторые идеи, я думаю, я могу пойти с этим:

public void Foo<T>(IList<T> collection)
{
    this.Foo(collection, collection.Count, i => collection[i]);
}

public void Foo<T>(IReadOnlyList<T> collection)
{
    this.Foo(collection, collection.Count, i => collection[i]);
}

private void Foo<T>(
    IEnumerable<T> collection,
    int count,
    Func<int, T> indexer)
{
    // Stuff
}

Изменить 2: Или я могу просто принять IReadOnlyList<T> и предоставить вспомогательный помощник:

public static class CollectionEx
{
    public static IReadOnlyList<T> AsReadOnly<T>(this IList<T> collection)
    {
        if (collection == null)
            throw new ArgumentNullException("collection");

        // I now check whether it already is IReadOnlyList<T>...
        // ...like casperOne has suggested.
        return collection as IReadOnlyList<T> 
            ?? new ReadOnlyWrapper<T>(collection);
    }

    private sealed class ReadOnlyWrapper<T> : IReadOnlyList<T>
    {
        private readonly IList<T> source;

        public int Count { get { return this.source.Count; } }
        public T this[int index] { get { return this.source[index]; } }

        public ReadOnlyWrapper(IList<T> source) { this.source = source; }

        public IEnumerator<T> GetEnumerator() { return this.source.GetEnumerator(); }
        IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
    }
}

Тогда я мог бы назвать Do(array.AsReadOnly())


Изменить 3:. Факт fun: массивы реализуют как IList<T> , так и IReadOnlyList<T>.

4b9b3361

Ответ 1

Вам здесь не повезло. IList<T> не реализует IReadOnlyList<T>. List<T> реализует оба интерфейса, но я думаю, что не то, что вы хотите.

Однако вы можете использовать LINQ:

  • Метод расширения Count() внутренне проверяет, действительно ли экземпляр является коллекцией, а затем использует свойство Count.
  • Метод расширения ElementAt() внутренне проверяет, действительно ли экземпляр является списком и чем использует индекс.

Ответ 2

Если вы больше беспокоитесь о том, чтобы поддерживать принципал DRY по производительности, вы можете использовать dynamic, например:

public void Do<T>(IList<T> collection)
{
    DoInternal(collection, collection.Count, i => collection[i]);
}
public void Do<T>(IReadOnlyList<T> collection)
{
    DoInternal(collection, collection.Count, i => collection[i]);
}

private void DoInternal(dynamic collection, int count, Func<int, T> indexer)
{
    // Get the count.
    int count = collection.Count;
}

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

  • Каждый вызов collection в DoInternal будет разрешен во время выполнения. Вы теряете безопасность типов, проверку времени компиляции и т.д.
  • Будет происходить ухудшение производительности (хотя и не серьезное, для сингулярного случая, но может быть при агрегировании)

Ваше предложение помощника является самым полезным, но я думаю, вы должны перевернуть его; что IReadOnlyList<T> интерфейс был представлен в .NET 4.5, многие API не поддерживают его, но имеют поддержку для IList<T>.

Таким образом, вы должны создать обертку AsList, которая принимает IReadOnlyList<T> и возвращает оболочку в реализации IList<T>.

Однако, если вы хотите подчеркнуть свой API, что вы берете IReadOnlyList<T> (чтобы подчеркнуть тот факт, что вы не мутируете данные), расширение AsReadOnlyList, которое у вас есть сейчас, будет более подходящим, но я бы сделал следующую оптимизацию для AsReadOnly:

public static IReadOnlyList<T> AsReadOnly<T>(this IList<T> collection)
{
    if (collection == null)
        throw new ArgumentNullException("collection");

    // Type-sniff, no need to create a wrapper when collection
    // is an IReadOnlyList<T> *already*.
    IReadOnlyList<T> list = collection as IReadOnlyList<T>;

    // If not null, return that.
    if (list != null) return list;

    // Wrap.
    return new ReadOnlyWrapper<T>(collection);
}

Ответ 3

Что вам нужно, это " IReadOnlyCollection <T> , доступный в .Net 4.5, который по существу является IEnumerable, который имеет" Count" в качестве свойство, но если вам нужна индексация, тогда вам понадобится IReadOnlyList <T> , который также даст индекс.

Я не знаю о вас, но я думаю, что этот интерфейс обязательно должен отсутствовать в течение очень долгого времени.

Ответ 4

Так как IList<T> и IReadOnlyList<T> не используют никакого полезного "предка", и если вы не хотите, чтобы ваш метод принимал какой-либо другой тип параметра, единственное, что вы можете сделать, это обеспечить две перегрузки.

Если вы решите, что повторное использование кодов является главным приоритетом, тогда вы можете перегрузить этот вызов методу private, который принимает IEnumerable<T>, и использует LINQ в том смысле, который предлагает Daniel, фактически позволяя LINQ выполнять нормализацию в во время выполнения.

Однако IMHO, вероятно, было бы лучше просто скопировать/вставить код один раз и просто сохранить две независимые перегрузки, которые различаются только по типу аргумента; Я не считаю, что микро-архитектура этого масштаба предлагает что-либо материальное, а с другой стороны, оно требует неочевидных маневров и медленнее.