Ответ
IReadOnlyList
Иерархия: IEnumerable<T> -> IReadOnlyCollection<T> -> IReadOnlyList<T>.
Пример использования:
// Метод возвращает защищенную от изменений коллекцию
public IReadOnlyList<string> GetActiveUsers()
{
List<string> internalList = FetchUsersFromDatabase();
// Возвращаем как IReadOnlyList, скрывая методы модификации
return internalList;
}
// Клиентский код
var users = GetActiveUsers();
Console.WriteLine(users[0]); // Доступ по индексу работает
Console.WriteLine(users.Count); // Получение количества работает
// users.Add("NewUser"); // Ошибка компиляции: IReadOnlyList не содержит метода Add
Ключевые моменты:
- Защита инкапсуляции: Это предпочтительный тип возвращаемого значения для методов, которые должны предоставлять данные, но не позволять их модифицировать. Это делает API более безопасным и предсказуемым.
- Не гарантирует неизменяемость объектов: Если
T— ссылочный тип, его поля могут быть изменены. Для полной иммутабельности нужны неизменяемые типы (record,readonly struct). - Производительность: Многие стандартные коллекции (
List<T>,Array,T[]) реализуют этот интерфейс, поэтому преобразование происходит без накладных расходов на копирование. - Альтернатива
IEnumerable: В отличие отIEnumerable<T>,IReadOnlyList<T>явно сообщает о возможности доступа по индексу и о наличии свойстваCount, что делает контракт API более информативным.
Ответ 18+ 🔞
А, слушай, ну это ж классика, блядь! IReadOnlyList
Понимаешь, это такой интерфейс, который орет на тебя: «Читать можно, а хуярить внутрь — нет!» То есть ты можешь смотреть на коллекцию, считать элементы, пальцем показывать — «дай мне вот этот, пятый по счёту» — но вот сунуть туда свой новый элемент или выкинуть старый — нихуя. Не-а.
Смотри, как оно по родне идёт: сначала просто IEnumerable<T> — с него можно только вытягивать данные, один за другим. Потом IReadOnlyCollection<T> — к нему добавили свойство Count, чтобы ты заранее знал, сколько тебе выгребать придётся. А IReadOnlyList<T> — это уже топчик, к нему ещё и индексатор прикрутили, чтобы ты мог по номеру, как в столовой, тыкать: «дай мне тот, что в синей чашке, на третьей полке!»
Вот, смотри, как на практике выглядит, чтоб не пиздеть просто так:
// Допустим, у тебя внутри метода своя кухня, свои списки
public IReadOnlyList<string> GetActiveUsers()
{
List<string> internalList = FetchUsersFromDatabase(); // тут ты наковырял из базы
// А возвращаешь как IReadOnlyList, чтобы умники снаружи не накосячили
return internalList; // вот это преобразование — оно бесплатное, блядь!
}
// А потом где-то в коде:
var users = GetActiveUsers();
Console.WriteLine(users[0]); // Всё, прочитал первого юзера — молодца
Console.WriteLine(users.Count); // Узнал, сколько их всего — умничка
// users.Add("NewUser"); // А вот это попробуй — компилятор тебе ебальник набьёт! IReadOnlyList не умеет добавлять, он же ReadOnly, ёпта!
Важный момент, который все просрать могут: этот интерфейс защищает саму коллекцию от изменений. То есть список не пополнишь и не почистишь. Но! Если внутри лежат объекты — ссылочные типы — то их внутреннее состояние менять можно, блядь! Представь: тебе выдали список аквариумов только для чтения. Сам ты аквариум выкинуть или новый поставить не можешь, а вот рыбок внутри каждого аквариума потрогать — запросто, хоть воду мути. Для полной, блять, неприкосновенности нужны иммутабельные типы, вроде record или readonly struct.
И ещё: это часто лучше, чем просто IEnumerable<T> возвращать. Потому что IEnumerable — он как тёмная комната: «иди, щупай, может там что-то есть». А IReadOnlyList — он уже свет включает и говорит: «вот, смотри, тут ровно 15 штук, и к каждой можно по индексу подойти». API сразу честнее и понятнее становится.
Короче, юзай его, когда хочешь данные наружу выдать, но при этом боишься, что их нахуй раздербанят.