В последнее время я рассматривал преимущества и недостатки возврата
Код: Выделить всё
IEnumerableС другой стороны, он настолько минимален, насколько это возможно в интерфейсе, поэтому он
оставляет вам как автору метода большую гибкость, чем использование
более тяжелой альтернативы, такой как IList или (не дай бог) массива.
Однако, как я описал в последнем посте, IEnumerable return
побуждает вызывающих нарушают принцип замены Лискова. Им слишком
легко использовать методы расширения LINQ, такие как Last() и Count(),
чья семантика IEnumerable не обещает.
Что требуется, так это лучший способ заблокировать возвращаемую коллекцию
без того, чтобы такие искушения были столь заметными. (Мне это напоминает Барни
Файфа, который усвоил этот урок на собственном горьком опыте.)
Введите IReadOnlyCollection, новую функцию в .NET 4.5. Он добавляет только одно
свойство в IEnumerable: свойство Count. Обещая подсчет,
вы гарантируете своим абонентам, что ваш IEnumerable действительно имеет
конечный пункт. Затем они могут с
чистой совестью использовать методы расширения LINQ, такие как Last().
Однако в этой статье рекомендуется использовать ReadOnlyCollection (и другие альтернативы) в случае, когда вы хотите защитить участников.
Итак, я обсуждал это с коллегой, и у нас разные мнения.
Он предлагает, чтобы метод возвращал значение. что бы оно ни имело (например, List или массив), если метод создал его, а вызывающая сторона изменила его впоследствии, это не повлияет ни на что другое (т. е. оно не является членом и не принадлежит чему-либо, чье время существования превышает время вызова метода).
Я считаю, что ReadOnlyCollection указывает вызывающему объекту, что коллекция обычно должна оставаться как есть. Если они хотят изменить его, то они должны явно привести его к IList или вызвать ToList для него.
В рекомендациях Microsoft, в частности, говорится:
В общем, отдавайте предпочтение ReadOnlyCollection.
Но, похоже, это не определяет общий случай, другой чем указывать использование Collection для возврата коллекций чтения/записи. Но считает ли мой коллега, что потребитель может захотеть добавить что-то в коллекцию и что нас не волнует, выполняют ли они определение для возврата коллекции для чтения/записи или нет?
РЕДАКТИРОВАТЬ
В ответ на некоторые ответы, чтобы попытаться добавить больше контекста к моему вопросу, я разместил ниже код, демонстрирующий различные сценарии с комментариями, относящимися к моему вопросу:
Код: Выделить всё
class Foo
{
private readonly int[] array = { 1 };
private readonly List list = new List(new[] {1});
// Get member array.
public int[] GetMemberArrayAsIs() => array;
public IEnumerable GetMemberArrayAsEnumerable() => array;
public ReadOnlyCollection GetMemberArrayAsReadOnlyCollection() => new ReadOnlyCollection(array);
// Get local array.
public int[] GetLocalArrayAsIs() => new[] { 1 };
public IEnumerable GetLocalArrayAsEnumerable() => new[] { 1 };
public ReadOnlyCollection GetLocalArrayAsReadOnlyCollection() => new ReadOnlyCollection(new[] { 1 });
// Get member list.
public Collection GetMemberListAsIs() => new Collection(list);
public IEnumerable GetMemberListAsEnumerable() => array;
public ReadOnlyCollection GetMemberListAsReadOnlyCollection() => new ReadOnlyCollection(array);
// Get local list.
public Collection GetLocalListAsIs() => new Collection(new[] { 1 });
public IEnumerable GetLocalListAsEnumerable() => new List(new[] { 1 });
public ReadOnlyCollection GetLocalListAsReadOnlyCollection() => new List(new[] { 1 }).AsReadOnly();
}
class FooTest
{
void Test()
{
var foo = new Foo();
int count;
// Get member array.
var array1 = foo.GetMemberArrayAsIs(); // ReSharper encourages to make the return type IEnumerable.
count = array1.Length; // ...unless we do this.
var enumerable1 = foo.GetMemberArrayAsEnumerable();
enumerable1.Concat(enumerable1); // Warning of possible multiple enumeration.
var roc1 = foo.GetMemberArrayAsReadOnlyCollection(); // ReSharper encourages to make the return type IEnumerable.
count = roc1.Count; // ...unless we do this.
// Get local array.
var array2 = foo.GetLocalArrayAsIs(); // ReSharper encourages to make the return type IEnumerable.
count = array2.Length; // ...unless we do this.
var enumerable2 = foo.GetLocalArrayAsEnumerable();
enumerable2.Concat(enumerable2); // Warning of possible multiple enumeration.
var roc2 = foo.GetLocalArrayAsReadOnlyCollection(); // ReSharper encourages to make the return type IEnumerable.
count = roc2.Count; // ...unless we do this.
// Get member list.
var list1 = foo.GetMemberListAsIs(); // ReSharper encourages to make the return type IEnumerable.
count = list1.Count; // ...unless we do this.
list1.Add(2); // This affects the Foo object as the collection is a member. DANGEROUS!
var enumerable3 = foo.GetMemberListAsEnumerable();
enumerable3.Concat(enumerable3); // Warning of possible multiple enumeration.
var roc3 = foo.GetMemberListAsReadOnlyCollection(); // ReSharper encourages to make the return type IEnumerable.
count = roc3.Count; // ...unless we do this.
// Get local list.
var list2 = foo.GetLocalListAsIs(); // ReSharper encourages to make the return type IEnumerable.
count = list2.Count; // ...unless we do this.
list2.Add(2); // This doesn't affect the Foo object as the collection was produced by the method.
var enumerable4 = foo.GetLocalListAsEnumerable();
enumerable4.Concat(enumerable4); // Warning of possible multiple enumeration.
var roc4 = foo.GetLocalListAsReadOnlyCollection(); // ReSharper encourages to make the return type IEnumerable.
count = roc4.Count; // ...unless we do this.
}
}
Код: Выделить всё
GetMemberArrayAsIsВ качестве альтернативы я мог бы настоять на возврате IEnumerable и возложить на вызывающую сторону ответственность за перечисление перед использованием коллекции несколько раз, но я думаю, что предпочтение Microsoft использовать ReadOnlyCollection состоит в том, чтобы избежать именно этого.
В случае List дилемма немного сложнее из-за возможности этого может быть изменен, что является проблемой, если он является членом. В этом случае я обязательно должен вернуть ReadOnlyCollection (
Код: Выделить всё
GetMemberListAsReadOnlyCollection). Но в случае локального списка должен ли я возвращать ReadOnlyCollectionКод: Выделить всё
GetMemberListAsIsНадеюсь, это добавит больше контекста к вопросу и в некоторых отношениях станет довольно философским. Но во многом речь идет о том, как следует интерпретировать рекомендации Microsoft и следует ли возлагать ответственность за преобразование в ReadOnlyCollection на поставщика или на потребителя, который должен перечислять возвращаемое значение перед его использованием несколько раз (с помощью ToArray), или же не делать этого и возвращать как есть.