Прежде чем неизменность, IEnumerable стал интерфейсом go-to во многих API-интерфейсах, поскольку это имело то преимущество, что API был нечувствителен к фактическому типу переданного объекта.
public void DoSomeEnumerationsWithACollection(IEnumerable<Thing> collectionOfThings)
{
foreach (var thing in collectionOfThings) doSomethingWith(thing);
foreach (var thing in collectionOfThings) doSomethingElseWith(thing);
}
Конечно, есть по крайней мере два недостатка:
-
Код API не может полагаться на неизменность collectionOfThings и может столкнуться с "изменением коллекции" или использовать другие другие тонкие проблемы.
-
Мы не знаем, является ли collectionOfThings реальной коллекцией или просто отложенным запросом. Если мы предположим, что это настоящая коллекция, и мы не рискуем снизить производительность, запустив несколько перечислений. Если мы предположим, что это отложенный запрос, и он фактически представляет собой реальную коллекцию, а затем превратить ее в локальный список или другую замороженную коллекцию, она требует ненужных затрат, хотя она помогает защитить нас от первой проблемы (все еще есть условие гонки при выполнении операции "ToList" ). Очевидно, мы можем написать небольшой код, чтобы проверить это и попытаться сделать "правильную вещь", но это раздражает лишний беспорядок.
Я должен признать, что никогда не нашел удовлетворительного шаблона для решения этой проблемы, кроме как использовать соглашения об именах. Прагматичный подход, казалось, заключался в том, что IEnumerable был самым низким методом трения для обхода коллекций, несмотря на недостатки.
Теперь с неизменяемыми коллекциями ситуация значительно улучшилась...
public void DoSomeEnumerationsWithACollection(ImmutableList<Thing> collectionOfThings)
{
Теперь больше нет возможности модификации коллекции, и нет никакой двусмысленности в отношении влияния производительности на множественные перечисления.
Однако мы, очевидно, потеряли гибкость в API, так как теперь нам нужно передать ImmutableList. Если у нашего клиента была какая-то другая перечислимая неизменяемая коллекция, ее нужно было бы скопировать в ImmutableList, чтобы ее можно было потреблять, хотя все, что мы хотим сделать, это перечислить ее.
В идеале мы могли бы использовать интерфейс, например
public void DoSomeEnumerationsWithACollection(IImmutableEnumerable<Thing> collectionOfThings)
но, конечно, интерфейс не может обеспечить семантику, такую как неизменяемость, кроме как по соглашению.
Использование базового класса может работать
public void DoSomeEnumerationsWithACollection(ImmutableEnumerableBase<Thing> collectionOfThings)
за исключением того, что он считал плохую форму для создания незапечатанных неизменяемых классов, чтобы подкласс не вводил изменчивость. И в любом случае это не было сделано в BCL.
Или мы могли бы просто использовать IEnumerable в API и использовать соглашение об именах, чтобы очистить наш код, полагаясь на неизменяемую коллекцию, которая должна быть передана.
Итак... мой вопрос в том, какие шаблоны считаются лучшими при передаче вокруг неизменяемых коллекций? Является ImmutableList "новым IEnumerable", когда мы начнем использовать immutablity или есть лучший способ?
Update
IReadOnlyCollection (предложенный Ювалем Ицчаковым ниже) явно отличается от IEnumerable, но все еще не полностью защищает пользователя от неконтролируемых изменений в коллекции. Примечательно, что кодовая база Roslyn сильно использует неизменяемость (в основном через ImmutableArray) и, по-видимому, использует явное типирование при передаче этих данных в другие методы, хотя есть несколько мест, где ImmutableList s передаются в методы, принимающие IEnumerable.