Зачем использовать IList или List?

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

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

Скажите, есть ли у меня этот метод

  public class SomeClass
    {
        public bool IsChecked { get; set; }
    }

 public void LogAllChecked(IList<SomeClass> someClasses)
    {
        foreach (var s in someClasses)
        {
            if (s.IsChecked)
            {
                // log 
            }

        }
    }

Я не уверен, как в будущем с помощью IList поможет мне.

Как насчет того, если я уже в методе? Должен ли я использовать IList?

public void LogAllChecked(IList<SomeClass> someClasses)
    {
        //why not List<string> myStrings = new List<string>()
        IList<string> myStrings = new List<string>();

        foreach (var s in someClasses)
        {
            if (s.IsChecked)
            {
                myStrings.Add(s.IsChecked.ToString());
            }

        }
    }

Что я могу использовать для использования IList сейчас?

public IList<int> onlySomeInts(IList<int> myInts)
    {
        IList<int> store = new List<int>();
        foreach (var i in myInts)
        {
            if (i % 2 == 0)
            {
                store.Add(i);
            }
        }

        return store;
    }

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

Итак, в основном мне нужно увидеть некоторые фактические примеры кода, как если бы я, где использовать IList, решил бы какую-то проблему, просто беря List во все.

Из моего чтения я думаю, что я мог бы использовать IEnumberable вместо IList, потому что я просто перебираю вещи.

Edit Поэтому я играл с некоторыми из своих методов о том, как это сделать. Я все еще не уверен относительно типа возврата (если я должен сделать его более конкретным или интерфейсом)

 public class CardFrmVm
    {
        public IList<TravelFeaturesVm> TravelFeaturesVm { get; set; }
        public IList<WarrantyFeaturesVm> WarrantyFeaturesVm { get; set; }

        public CardFrmVm()
        {
            WarrantyFeaturesVm = new List<WarrantyFeaturesVm>();
            TravelFeaturesVm = new List<TravelFeaturesVm>();
        }
}

 public class WarrantyFeaturesVm : AvailableFeatureVm
    {
    }

     public class TravelFeaturesVm : AvailableFeatureVm
    {

    }

      public class AvailableFeatureVm
    {
        public Guid FeatureId { get; set; }
        public bool HasFeature { get; set; }
        public string Name { get; set; }
    }


        private IList<AvailableFeature> FillAvailableFeatures(IEnumerable<AvailableFeatureVm> avaliableFeaturesVm)
        {
            List<AvailableFeature> availableFeatures = new List<AvailableFeature>();
            foreach (var f in avaliableFeaturesVm)
            {
                if (f.HasFeature)
                {
                                                    // nhibernate call to Load<>()
                    AvailableFeature availableFeature = featureService.LoadAvaliableFeatureById(f.FeatureId);
                    availableFeatures.Add(availableFeature);
                }
            }

            return availableFeatures;
        }

Итак, я прямо сейчас возвращаю IList для простого факта, что я добавлю это в свою модель домена, что имеет свойство вроде этого

public virtual IList<AvailableFeature> AvailableFeatures { get; set; }

вышеупомянутое является самим IList, поскольку это то, что кажется стандартом для использования с nhibernate. В противном случае я мог бы вернуть IEnumberable обратно, но не уверен. Все еще не может понять, что пользователю нужно 100% (что, когда возврат бетона имеет преимущество).

Изменить 2

Я также думал о том, что произойдет, если я захочу выполнить передачу по ссылке в моем методе?

private void FillAvailableFeatures(IEnumerable<AvailableFeatureVm> avaliableFeaturesVm, IList<AvailableFeature> toFill)
            {

                foreach (var f in avaliableFeaturesVm)
                {
                    if (f.HasFeature)
                    {
                                                        // nhibernate call to Load<>()
                        AvailableFeature availableFeature = featureService.LoadAvaliableFeatureById(f.FeatureId);
                        toFill.Add(availableFeature);
                    }
                }
            }

У меня возникнут проблемы с этим? Так как они не могут пройти в массив (который имеет фиксированный размер)? Было бы лучше, может быть, для конкретного списка?

Ответ 1

Здесь три вопроса: какой тип я должен использовать для формального параметра? Что я должен использовать для локальной переменной? и что я должен использовать для возвращаемого типа?

Формальные параметры:

Принцип здесь не требует больше, чем вам нужно. IEnumerable<T> сообщает: "Мне нужно получить элементы этой последовательности от начала до конца". IList<T> сообщает: "Мне нужно получить и установить элементы этой последовательности в произвольном порядке". List<T> сообщает: "Мне нужно получить и установить элементы этой последовательности в произвольном порядке, и я принимаю только списки, я не принимаю массивы".

Прося больше, чем вам нужно, вы (1) заставляете вызывающего делать ненужную работу для удовлетворения ваших ненужных требований и (2) сообщают читателю ложь. Спросите только, что вы собираетесь использовать. Таким образом, если у вызывающего есть последовательность, им не нужно вызывать ToList на нем, чтобы удовлетворить ваше требование.

Локальные переменные:

Используйте все, что хотите. Это ваш метод. Вы единственный, кто видит внутренние детали реализации метода.

Тип возврата:

Тот же принцип, что и раньше, обратный. Предложите минимальный минимальный уровень, который требует ваш вызывающий абонент. Если вызывающий абонент требует только перечисления последовательности, укажите только IEnumerable<T>.

Ответ 2

Самая практическая причина, которую я когда-либо видел, дал Джеффри Рихтер в CLR через С#.

Образец - использовать для ваших аргументов самый низкий класс или интерфейс и возвращать наиболее подходящий класс или интерфейс для ваших возвращаемых типов. Это дает вашим абонентам большую гибкость в передаче типов вашим методам и большинству возможностей для повторного использования возвращаемых значений.

Например, следующий метод

public void PrintTypes(IEnumerable items) 
{ 
    foreach(var item in items) 
        Console.WriteLine(item.GetType().FullName); 
}

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

public void PrintTypes(List items)

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

Говоря о типах возврата, чем более конкретными вы являетесь, тем более гибкими абонентами могут быть с ним.

public List<string> GetNames()

вы можете использовать этот тип возврата для повторения имен

foreach(var name in GetNames())

или вы можете индексировать непосредственно в коллекцию

Console.WriteLine(GetNames()[0])

В то время как если вы возвращаетесь к менее определенному типу

public IEnumerable GetNames()

вам нужно будет массировать возвращаемый тип, чтобы получить первое значение

Console.WriteLine(GetNames().OfType<string>().First());

Ответ 3

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

Если вы обнаружите свое свойство как List<T> или даже IList<T>, когда все, что вы хотите, чтобы ваш потребитель имел, - это возможность итерации по коллекции. Тогда они могут зависеть от того, что они могут изменить список. Затем позже, если вы решите преобразовать фактическое хранилище данных с List<T> в Dictionary<T,U> и выставить ключи словаря в качестве фактического значения для свойства (я должен был сделать именно это раньше). Тогда потребители, которые ожидают, что их изменения будут отражены внутри вашего класса, больше не будут иметь такую ​​возможность. Это большая проблема! Если вы обнаружите List<T> как IEnumerable<T>, вы можете с уверенностью предсказать, что ваша коллекция не будет изменяться извне. Это одна из возможностей разоблачения List<T> как любого из вышеперечисленных интерфейсов.

Этот уровень абстракции идет в другом направлении, когда он принадлежит параметрам метода. Когда вы передаете свой список методу, который принимает IEnumerable<T>, вы можете быть уверены, что ваш список не будет изменен. Когда вы являетесь человеком, реализующим этот метод, и вы говорите, что принимаете IEnumerable<T>, потому что все, что вам нужно сделать, это перебрать этот список. Затем человек, вызывающий метод, может называть его любым типом данных, который можно перечислить. Это позволяет использовать ваш код неожиданным, но вполне допустимым способом.

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

Вы не можете предсказать будущее. Предполагая, что тип свойства всегда будет полезен, поскольку List<T> немедленно ограничивает вашу способность адаптироваться к непредвиденным ожиданиям вашего кода. Да, вы никогда не сможете изменить этот тип данных из List<T>, но можете быть уверены, что если вам нужно. Ваш код готов к этому.

Ответ 4

Короткий ответ:

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

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

Прочитайте немного о наследовании и полиморфизме.

Ответ 5

Вот пример: у меня был проект, когда наши списки стали очень большими, а результирующая фрагментация кучи большого объекта ухудшала производительность. Мы заменили List на LinkedList. LinkedList не содержит массив, поэтому внезапно мы почти не использовали кучу больших объектов.

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

В конце концов, вам может потребоваться заменить реализацию по любой причине; производительность - это всего лишь одна возможность. Вне зависимости от причины использование наименее производного типа уменьшит потребность в изменениях в вашем коде при изменении определенного типа времени выполнения ваших объектов.

Ответ 6

Внутри метода вы должны использовать var вместо IList или List. Когда ваш источник данных изменится, исходя из метода, ваш метод onlySomeInts сохранится.

Причиной использования IList вместо List в качестве параметров является то, что многие вещи реализуют IList (List и [] как два примера), но только одна вещь реализует List. Это более гибко для кодирования интерфейса.

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

Ответ 7

Используя IList вместо List, значительно упростите тестовые тесты. Это позволяет вам использовать библиотеку "Mocking" для передачи и возврата данных.

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

Рассмотрим (надуманный) случай, когда у меня есть объект данных, который реализует IList.

public class MyDataObject : IList<int>
{
    public void Method1()
    {
       ...
    }
    // etc
}

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

В вашем примере IEnumerable - лучший выбор, как вы думали.

Ответ 8

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

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

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

Однако, если вы не пишете фреймворк, может быть полезно передать параметр со слабыми возможными типами (т.е. базовые классы, интерфейсы или даже делегаты) и вернуть конкретные типы. Это дает вызывающей способности возможность делать как можно больше с возвращенным объектом, даже если он отображается как интерфейс. Однако это делает метод более хрупким, так как любое изменение типа возвращаемого объекта может нарушить код вызова. На практике, хотя это, как правило, не является серьезной проблемой.

Ответ 9

Вы принимаете интерфейс как параметр для метода, потому что это позволяет вызывающему пользователю передавать различные конкретные типы в качестве аргументов. Учитывая ваш метод метода LogAllChecked, параметр someClasses может быть разных типов, а для человека, пишущего метод, все могут быть эквивалентными (т.е. Вы будете писать тот же самый код независимо от типа параметра). Но для человека, вызывающего метод, он может иметь огромное значение - если у них есть массив, и вы запрашиваете список, они должны изменить массив на список или v.v. всякий раз, когда вы вызываете метод, теряете время как от программиста, так и от производительности POV.

Если вы вернете интерфейс или конкретный тип, зависит от того, что вы хотите позволить своим абонентам делать с созданным вами объектом - это решение для проектирования API, и нет жесткого и быстрого правила. Вы должны взвесить их способность полностью использовать объект против их способности легко использовать часть функциональности объектов (и, конечно, хотите ли вы, чтобы они полностью использовали объект). Например, если вы возвращаете IEnumerable, то вы ограничиваете их итерацией - они не могут добавлять или удалять элементы из вашего объекта, они могут действовать только против объектов. Если вам нужно выставить коллекцию за пределами класса, но не хотите, чтобы вызывающая сторона меняла коллекцию, это один из способов ее выполнения. С другой стороны, если вы возвращаете пустую коллекцию, которую вы ожидаете/хотите, чтобы она заполнялась, то IEnumerable непригоден.

Ответ 10

Здесь мой ответ в этом .NET 4.5 + мире.

Используйте IList <T> и IReadonlyList <T> ,
                            не существует.

IList < Т > выглядит так согласуется с IReadonlyList <T>

  • Использовать IEnumerable <T> для минимального воздействия (свойства) или требования (параметра), если foreach является единственным способом его использования.
  • Использовать IReadonlyList <T> если вам также нужно выставить/использовать Count и [] indexer.
  • Использовать IList <T> если вы также разрешаете абонентам добавлять/обновлять/удалять элементы

потому что List <T> реализует IReadonlyList <T> , он не требует какого-либо явного литья.

Пример класса:

// manipulate the list within the class
private List<int> _numbers;

// callers can add/update/remove elements, but cannot reassign a new list to this property
public IList<int> Numbers { get { return _numbers; } }

// callers can use: .Count and .ReadonlyNumbers[idx], but cannot add/update/remove elements
public IReadOnlyList<int> ReadonlyNumbers { get { return _numbers; } }