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

IEnumerable <T> против массива

Я пытаюсь понять, , что было бы лучшим способом опубликовать Readonly List объектов как общедоступный метод? Из Блог Эрика Липперта, Массивы нехорошо, потому что кто-то может легко добавить новую запись. Поэтому каждый раз при вызове метода нужно было бы передавать новый массив. Он предлагает передать IEnumerable<T>, так как это определение только для чтения (без добавления, удаления методов), которое я практиковал довольно давно. Но в нашем новом проекте люди даже начали создавать массивы этих IEnumerables, потому что они не знают DataSource позади, поэтому они получают следующее: Предупреждение об обработке для возможного множественного перечисления IEnumerable

Меня интересует технический подход, как можно решить эту загадку. Единственное решение, которое я придумал до сих пор, - это использовать IReadOnlyCollection, но это будет более явным, чем IEnumerable.

Какова наилучшая практика для публикации таких списков, которые не должны быть изменены, но должны быть объявлены в виде списков памяти?

4b9b3361

Ответ 1

Обычно - и с течением времени - это решалось с помощью неизменных коллекций.

Ваши общедоступные свойства должны быть, например, типа IImmutableList<T>, IImmutableHashSet<T> и т.д.

Любые IEnumerable<T> могут быть преобразованы в неизменяемую коллекцию:

  • someEnumerable.ToImmutableList();
  • someEnumerable.ToImmutableHashSet();
  • ... и т.д.

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

Например:

public class A
{
     private List<string> StringListInternal { get; set; } = new List<string>();
     public IImmutableList<string> StringList => StringListInternal.ToImmutableList();
}

Также существует альтернативный подход с использованием интерфейсов:

public interface IReadOnlyA
{
     IImmutableList<string> StringList { get; }
}

public class A : IReadOnlyA
{
     public List<string> StringList { get; set; } = new List<string>();
     IImmutableList<string> IReadOnlyA.StringList => StringList.ToImmutableList();
}

Убедитесь, что IReadOnlyA явно реализовано, поэтому оба изменяемых и неизменяемых свойства StringList могут сосуществовать как часть тот же класс.

Если вы хотите выставить неизменяемый A, то вы вернете объекты A, повысившиеся до IReadOnlyA, и верхние слои не смогут мутировать весь StringList в примере выше:

public IReadOnlyA DoStuff()
{
     return new A();
}

IReadOnlyA a = DoStuff();

// OK! IReadOnly.StringList is IImmutableList<string>
IImmutableList<string> stringList = a.StringList;

Предотвращение преобразования изменяемого списка в неизменяемый список каждый раз

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

Разделимые элементы

Если тип элементов переопределяет Object.Equals и GetHashCode и опционально реализует IEquatable<T>, то доступ к общедоступным свойствам неизменяемого списка может выглядеть следующим образом:

public class A : IReadOnlyA
{
     private IImmutableList<string> _immutableStringList;

     public List<string> StringList { get; set; } = new List<string>();

     IImmutableList<string> IReadOnlyA.StringList
     {
         get
         {
             // An intersection will verify that the entire immutable list
             // contains the exact same elements and count of mutable list
             if(_immutableStringList.Intersect(StringList).Count == StringList.Count)
                return _immutableStringList;
             else
             {
                  // the intersection demonstrated that mutable and
                  // immutable list have different counts, thus, a new
                  // immutable list must be created again
                 _immutableStringList = StringList.ToImmutableList();

                 return _immutableStringList;
             }
         }
     }
}

Ответ 2

Я не думаю, что непреложный способ пойти

int[] source = new int[10000000];//uses 40MB of memory
var imm1 = source.ToImmutableArray();//uses another 40MB
var imm2 = source.ToImmutableArray();//uses another 40MB

Список ведет себя одинаково. Если я хочу делать полную копию каждый раз, мне не нужно заботиться о том, что пользователь делает с этим массивом. Обеспечение неизменности не защищает содержимое объектов в коллекции, их можно свободно менять. Предложение @HansPassant кажется лучшим

public class A
{
    protected List<int> list = new List<int>(Enumerable.Range(1, 10000000));
    public IReadOnlyList<int> GetList
    {
        get { return list; }
    }
}

Ответ 3

Для коллекции, которую вы не собираетесь изменять, IEnumerable<T> по-прежнему, вероятно, самый безопасный вариант, плюс он позволяет вставлять любой тип коллекции, а не только массивы. Причина этого предупреждения связана с тем, что IEnumerable представляет запрос, который использует отложенное выполнение, что означает, что потенциально дорогостоящая операция может выполняться несколько раз. Обратите внимание, что нет интерфейса, который различает коллекции в памяти и потенциально отложенные оболочки. Этот вопрос задан до.

Исправление для этого не перечисляет источник несколько раз. Если коду необходимо выполнить несколько iteartions (что может быть законным), то гидрируйте коллекцию до List<T> перед повторением.

Ответ 4

IEnumerable - это интерфейс только для чтения, который скрывает реализацию от пользователя. Некоторые могут утверждать, что пользователь может использовать IEnumerable для добавления и добавления новых элементов, но это означает две вещи:

  • Пользователь нарушает предоставленный API
  • Вы не можете отключить пользователя от использования рефлексии

IEnumerable описывает поведение, а List - реализация этого поведения. Когда вы используете IEnumerable, вы даете компилятору возможность отложить работу дольше, возможно, оптимизируя на этом пути. Если вы используете ToList(), вы вынуждаете компилятор немедленно подготовить результаты.

Я использую IEnumerable Всякий раз, когда вы работаете с выражениями LINQ, потому что, указав только поведение, я даю LINQ возможность отложить оценку и, возможно, оптимизировать программу.

Чтобы предотвратить любые изменения в объекте List<T>, выведите его только через оболочку ReadOnlyCollection<T>, которая не предоставляет методы, которые изменяют коллекцию. Однако, если изменения внесены в базовый объект List<T>, коллекция только для чтения отражает эти изменения, как описано в MSDN.

Посмотрите Новые коллекции только для чтения в .NET 4.5.

Ответ 5

Я программировал дольше, чем я помню, и у меня никогда не было такого требования, чтобы защитить список от изменения. Я не говорю, что это не возможное требование, я просто говорю, что очень редко нужно такое требование. Если у вас есть список, циркулирующий в вашем приложении, то, скорее всего, вам придется исправить свой дизайн. Если вам нужна помощь в этом, дайте нам, как вы используете список.

Примеры, которые вы даете в своем вопросе и комментариях, не являются хорошими примерами того, когда нужно требовать неизменяемый или только для чтения список. Давайте обсуждать их один за другим.

  • Вы упомянули о публикации его как API. По определению, все, что вы возвращаете из API, уже не ваше, и вам все равно, как он используется. Фактически, как только он покидает ваш API, он теперь находится в клиентском домене API, и они могут делать с ними все, что захотят. Помимо того факта, что вы не можете защитить его, когда он находится в их домене, вы не должны диктовать, как они будут его использовать. Что еще более важно, вы никогда не должны принимать что-либо в качестве входа в свой API, даже если это тот же список, который вы вернули ранее, и считаете, что вы защищены. Весь вход ДОЛЖЕН быть проверен соответствующим образом.

  • Возможно, вы на самом деле не имели в виду API, но больше похожи на библиотеку DLL, которую вы используете в своих проектах. Является ли это DLL или просто классом в вашем проекте, применяется тот же принцип. Когда вы возвращаете что-то, пользователь должен использовать его. И вы никогда не должны принимать одно и то же (список или что-то еще) обратно без проверки. Аналогично, вы никогда не должны выставлять внутренний член своего класса, будь то список или одно значение. В конце концов, вся идея использования свойств вместо того, чтобы маркировать участников как общедоступных. Когда вы создаете свойство для однозначного элемента, возвращается копия значения. Аналогично, вы должны создать свойство для своего списка и вернуть копию, а не сам список.

Если вам действительно нужен список, доступный по всему миру, и вы хотите загрузить его только один раз, выставляете его другим классам, защищайте его от модификации, и он слишком велик, чтобы сделать его копию. Вы можете посмотреть некоторые проекты, например, обернуть его в Singleton и/или сделать его доступным только для чтения в соответствии с ответом Антонина Лейсека. Фактически, его ответ можно легко преобразовать в Singleton, отметив конструктор как частный, благодаря упрощенной реализации Singleton на С#.