Есть ли что-нибудь волшебство в ReadOnlyCollection

Наличие этого кода...

var b = new ReadOnlyCollection<int>(new[] { 2, 4, 2, 2 });
b[2] = 3;

Я получаю ошибку компиляции во второй строке. Я ожидал бы ошибки во время выполнения, поскольку ReadOnlyCollection<T> реализует IList<T>, а this[T] - сеттер в интерфейсе IList<T>.

Я попытался реплицировать функциональность ReadOnlyCollection, но удаление setter из this[T] является ошибкой компиляции.

Ответ 1

indexer реализуется с явной реализацией интерфейса, поэтому вы сможете получить доступ к нему, если вы это сделаете:

IList<int> b = new ReadOnlyCollection<int>(new[] { 2, 4, 2, 2 });
b[2] = 3;

или

var b = new ReadOnlyCollection<int>(new[] { 2, 4, 2, 2 });
((IList<int>)b)[2] = 3;

Конечно, он будет терпеть неудачу во время выполнения...

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

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

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

T IList<T>.this[int index]
{
    // Delegate interface implementation to "normal" implementation
    get { return this[index]; }
    set { throw new NotSupportedException("Collection is read-only."); }
}

public T this[int index]
{
    get { return ...; }
}

Ответ 2

Он явно реализует IList.Items, что делает его непубличным, и вам нужно будет передать его в интерфейс, чтобы достичь его реализации, и внедряет новый этот [...] indexer, который используется вместо этого, который только имеет get-accessor.

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

К сожалению, я не знаю, как это сделать на С#, поскольку запись индексатора в С# включает в себя использование ключевого слова this, и вы не можете записать это:

T IList<T>.this[int index] { get; set; }

Ответ 3

Нет волшебства, ReadOnlyCollection просто имеют разные реализации для своего собственного индексатора и индексатора, реализующего интерфейс IList<T>:

public T Item[int index] { get; }

T IList<T>.Item[int index] { get; set; }

Если вы перечислите свой список в IList<int>, вы получите ошибку времени выполнения вместо ошибки компиляции:

((IList<int>)b)[2] = 3;

Edit:
Чтобы реализовать индексатор в своем классе, вы используете ключевое слово this:

public T this[int index] { get { ... } }

T IList<T>.this[int index] { get { ... } set { ... } }