views:

235

answers:

3

Having this code...

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

I get a compile error at the second line. I would expect a runtime error since ReadOnlyCollection<T> implements IList<T> and the this[T] have a setter in the IList<T> interface.

I've tried to replicate the functionality of ReadOnlyCollection, but removing the setter from this[T] is a compile error.

+2  A: 

It implements IList.Items explicitly, which makes it non-public, and you'll have to cast to the interface to reach its implementation, and implements a new this[...] indexer, which is used instead, which only has a get-accessor.

If you cast the collection to IList, your code will compile, but will fail at runtime instead.

Unfortunately I don't know how to do this in C#, since writing an indexer in C# involves using the this keyword, and you can't write this:

T IList<T>.this[int index] { get; set; }
Lasse V. Karlsen
@Lasse: You can write that - with appropriate implementations. The problem is that you can't implement one half explicitly and one half implicitly, as far as I can tell.
Jon Skeet
You don't have to, if you can write that, you write the setter with just throwing an exception, and then you implement a public this[..] indexer with just the getter.
Lasse V. Karlsen
+11  A: 

The indexer is implemented with explicit interface implementation, so you'll only be able to access it if you do:

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

or

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

Of course, it'll then fail at execution time...

This is entirely deliberate and helpful - it means that when the compiler knows it's a ReadOnlyCollection, the unsupported bits of functionality aren't available to you, helping to divert you away from execution time failure.

It's an interesting and relatively unusual step though, effectively implementing one half of a property/indexer implicitly, and one half explicitly.

Contrary to my previous thoughts, I believe ReadOnlyCollection<T> actually implements the whole indexer explicitly, but also provides a public readonly indexer. In other words, it's something like this:

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 ...; }
}
Jon Skeet
Ok, but how do i replicate the functionality of ReadOnlyCollection using explicit implementation. I don't see how you can remove a method or property from the interface.
Esben Skov Pedersen
@EsbenP: You can't remove a method from the interface... but you can make it only available when the static type of the reference is the interface rather than the class implementing the interface.
Jon Skeet
Ok, if I have two indexers, one of them implementing IList explicitly it worksT IList<T>.this[int index]{ get { return source[index]; } set { throw new NotImplementedException(); }}public T this[int index]{ get { return source[index]; }}
Esben Skov Pedersen
You've nailed how the indexers are implemented with your latest edit.
Lasse V. Karlsen
+1  A: 

There is no magic, the ReadOnlyCollection just have different implementations for it's own indexer and the indexer that implements the IList<T> interface:

public T Item[int index] { get; }

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

If you cast your list to IList<int>, you will get a runtime error instead of the compilation error:

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

Edit:
To implement the indexer in your own class, you use the this keyword:

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

T IList<T>.this[int index] { get { ... } set { ... } }
Guffa
That was my thought as well, but when i try that in my own class implementing IList<T> it will not compile
Esben Skov Pedersen
What's the compile error message?
thecoop
@EsbenP: To implement it in a class the syntax is not the same as the signature shown in the documentation. See the edit above.
Guffa