Я знаю, что по этому поводу было много сообщений, но меня все еще смущает, почему вы должны передавать интерфейс, такой как 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, поскольку я просто просматриваю материал.
Редактировать Итак, я экспериментировал с некоторыми из моих методов, как это сделать. Я до сих пор не уверен в возвращаемом типе (нужно ли сделать его более конкретным или интерфейсом).
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);
}
}
}
у меня возникнут проблемы с этим? Поскольку они не могли передать массив (имеющий фиксированный размер)? Может быть, лучше для конкретного списка?
Count
работает в O (1) , если вашIEnumerable
естьICollection
. Конечно, вы также можете просто использоватьforeach
цикл.items as IList<T>
и если вы получите ненулевое значение, используйте улучшенный код. Таким образом вы воспользуетесь преимуществом, если сможете, но при этом предоставите клиенту гибкость в том, что он передает.Самая практическая причина, которую я когда-либо видел, была дана Джеффри Рихтером в CLR через C #.
Шаблон состоит в том, чтобы взять самый базовый класс или интерфейс, возможный для ваших аргументов, и вернуть наиболее конкретный класс или интерфейс, возможный для ваших возвращаемых типов. Это дает вашим вызывающим объектам наибольшую гибкость при передаче типов вашим методам и наибольшие возможности для приведения / повторного использования возвращаемых значений.
Например, следующий метод
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());
источник
IEnumerable<T>
позволяет перебирать коллекцию.ICollection<T>
основывается на этом, а также позволяет добавлять и удалять элементы.IList<T>
также позволяет получать доступ к ним и изменять их по определенному индексу. Раскрывая тот, с которым, как вы ожидаете, будет работать ваш потребитель, вы можете изменить свою реализацию.List<T>
реализует все три этих интерфейса.Если вы выставляете свое свойство как объект
List<T>
или даже как объект,IList<T>
когда все, что вы хотите, чтобы ваш потребитель имел, - это возможность итерации по коллекции. Тогда они могут зависеть от того факта, что они могут изменять список. Позже, если вы решите преобразовать фактическое хранилище данных из aList<T>
в aDictionary<T,U>
и предоставить ключи словаря как фактическое значение свойства (мне приходилось делать именно это раньше). Тогда потребители, которые ожидали, что их изменения будут отражены внутри вашего класса, больше не будут иметь этой возможности. Это большая проблема! Если вы открываетеList<T>
как an,IEnumerable<T>
вы можете с уверенностью предсказать, что ваша коллекция не будет изменяться извне. Это одна из возможностей использованияList<T>
любого из вышеперечисленных интерфейсов.Этот уровень абстракции идет в другом направлении, когда он принадлежит параметрам метода. Когда вы передаете свой список методу, который принимает,
IEnumerable<T>
вы можете быть уверены, что ваш список не будет изменен. Когда вы являетесь лицом, реализующим метод, и говорите, что принимаете,IEnumerable<T>
потому что все, что вам нужно сделать, - это пройтись по этому списку. Затем человек, вызывающий метод, может вызвать его с любым перечислимым типом данных. Это позволяет использовать ваш код неожиданными, но вполне допустимыми способами.Из этого следует, что ваша реализация метода может представлять свои локальные переменные как угодно. Детали реализации не раскрываются. Предоставляя вам возможность изменить свой код на что-то лучшее, не затрагивая людей, вызывающих ваш код.
Вы не можете предсказать будущее. Предполагая, что тип свойства всегда будет полезен, поскольку это
List<T>
немедленно ограничивает вашу способность адаптироваться к непредвиденным ожиданиям вашего кода. Да, вы никогда не можете изменить этот тип данных с a,List<T>
но вы можете быть уверены, что если вам нужно. Ваш код готов к этому.источник
Краткий ответ:
Если вы используете конкретную реализацию списка, другая реализация того же списка не будет поддерживаться вашим кодом.
Прочтите немного о наследовании и полиморфизме .
источник
Вот пример: однажды у меня был проект, в котором наши списки стали очень большими, и в результате фрагментация кучи больших объектов ухудшила производительность. Мы заменили List на LinkedList. LinkedList не содержит массив, поэтому внезапно мы почти не использовали кучу больших объектов.
В большинстве случаев мы использовали списки
IEnumerable<T>
, так что дальнейшие изменения не требовались. (И да, я бы рекомендовал объявлять ссылки как IEnumerable, если все, что вы делаете, это их перечисление.) В некоторых случаях нам понадобился индексатор списков, поэтому мы написали неэффективнуюIList<T>
оболочку для связанных списков. Индексатор списка нужен нам нечасто, поэтому его неэффективность не проблема. Если бы это было так, мы могли бы предоставить какую-то другую реализацию IList, возможно, в виде набора достаточно маленьких массивов, которые можно было бы более эффективно индексировать, избегая при этом больших объектов.В конце концов, вам может потребоваться заменить реализацию по любой причине; производительность - лишь одна из возможностей. Независимо от причины использование наименее производного типа из возможных уменьшит необходимость изменений в вашем коде при изменении конкретного типа времени выполнения ваших объектов.
источник
Внутри метода вы должны использовать
var
вместоIList
илиList
. Когда ваш источник данных изменится на метод, вашonlySomeInts
метод останется в живых.Причина использования
IList
вместоList
параметров в том, что многие вещи реализуютсяIList
(List и [], как два примера), но реализуется только одна вещьList
. Код интерфейса более гибкий.Если вы просто перечисляете значения, вам следует использовать
IEnumerable
. Каждый тип данных, который может содержать более одного значения, реализуетIEnumerable
(или должен) и делает ваш метод чрезвычайно гибким.источник
Использование IList вместо List значительно упрощает написание модульных тестов. Это позволяет использовать библиотеку «Mocking» для передачи и возврата данных.
Другая общая причина использования интерфейсов - предоставить пользователю минимальный объем знаний, необходимых для работы с объектом.
Рассмотрим (надуманный) случай, когда у меня есть объект данных, реализующий IList.
public class MyDataObject : IList<int> { public void Method1() { ... } // etc }
Ваши функции, указанные выше, заботятся только о возможности перебора списка. В идеале им не нужно знать, кто реализует этот список или как они его применяют.
В вашем примере IEnumerable - лучший выбор, как вы думали.
источник
Всегда полезно максимально уменьшить зависимости между вашим кодом.
Имея это в виду, имеет смысл передавать типы с наименьшим возможным количеством внешних зависимостей и возвращать то же самое. Однако это может отличаться в зависимости от видимости ваших методов и их сигнатур.
Если ваши методы являются частью интерфейса, методы должны быть определены с использованием типов, доступных для этого интерфейса. Конкретные типы, вероятно, будут недоступны для интерфейсов, поэтому им придется возвращать неконкретные типы. Вы бы захотели сделать это, например, если бы создавали фреймворк.
Однако, если вы не пишете фреймворк, может быть полезно передать параметр с самыми слабыми из возможных типов (то есть базовыми классами, интерфейсами или даже делегатами) и вернуть конкретные типы. Это дает вызывающему объекту возможность делать с возвращенным объектом как можно больше, даже если он приведен как интерфейс. Однако это делает метод более хрупким, так как любое изменение типа возвращаемого объекта может нарушить вызывающий код. Однако на практике это обычно не является большой проблемой.
источник
Вы принимаете интерфейс в качестве параметра для метода, потому что это позволяет вызывающему объекту передавать различные конкретные типы в качестве аргументов. Учитывая ваш пример метода LogAllChecked, параметр someClasses может быть разных типов, и для человека, пишущего метод, все могут быть эквивалентными (то есть вы должны написать один и тот же код независимо от типа параметра). Но для человека, вызывающего метод, это может иметь огромное значение - если у них есть массив, и вы запрашиваете список, они должны изменить массив на список или vv при каждом вызове метода, полная трата время и от программиста, и от перформанса POV.
Возвращаете ли вы интерфейс или конкретный тип, зависит от того, что вы хотите разрешить своим вызывающим лицам делать с созданным вами объектом - это дизайнерское решение API, и здесь нет жесткого правила. Вы должны взвесить их способность в полной мере использовать объект с их способностью легко использовать часть функциональных возможностей объекта (и, конечно же, ХОТИТЕ ли вы, чтобы они использовали объект в полной мере). Например, если вы возвращаете IEnumerable, вы ограничиваете их итерацией - они не могут добавлять или удалять элементы из вашего объекта, они могут действовать только против объектов. Если вам нужно предоставить коллекцию вне класса, но вы не хотите позволять вызывающей стороне изменять коллекцию, это один из способов сделать это. С другой стороны, если вы возвращаете пустую коллекцию, которую ожидаете / хотите, чтобы они заполнялись,
источник
Вот мой ответ в мире .NET 4.5+ .
IList <T> выглядит так согласованно с IReadonlyList <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; } }
источник