ReadOnly Collections
A disadvantage of .NET is surely failed definition (and implementation) of read-only collections. From the beginning .NET creators simply ignored read-only collections. This brings a lot of misunderstanding among.NET developers.
Take a look at this class:
public class FirstFiveOddNumbers { List<int> numbers = new List<int>(new int[]{1,3,5,7,9}); . . . IList<int> GetNumbers() { return numbers; } }
Method GetNumbers() doesn't return read-only list, in fact caller can change, add, remove numbers in returned list.
Sure, method GetNumbers() can clone internal collection and return cloned one(new List<int>(numbers)), in such way method's class can ensure that caller's changes on collection will not affect its (internal) collection object. But cloning can be inefficient for large collections.
For that purpose Microsoft added in .NET 2.0 ReadOnlyCollection<T> wrapper:
[SerializableAttribute] [ComVisibleAttribute(false)] public class ReadOnlyCollection<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
Constructor of ReadOnlyCollection<T> accepts IList<T> that it wraps:
public ReadOnlyCollection(IList<T> list)
It only wraps a collection, so it is O(1).
So lets change our code:
public class FirstFiveOddNumbers { List<int> numbers = new List<int>(new int[]{1,3,5,7,9}); . . . ReadOnlyCollection<int> GetNumbers() { return new ReadOnlyCollection(numbers); } }
Now, caller is unable to change returned collection, caller can't even cast returned collection to List<int> and then change items of collection.
It seems, like that is what we want. Is it !?
The problem is that ReadOnlyCollection<T> is not covariant in T, like any other implementation (only interfaces can be type covariant).
This code demostrates a problem:
public interface Shape { } public class Rectangle : Shape { } public interface Geometry { ReadOnlyCollection<Shape> GetShapes(); } public class RectangleGeomerty : Geometry { List<Rectangle> rectangles; ReadOnlyCollection<Shape> GetShapes() { return new ReadOnlyCollection<Shape>(rectangles); } }
This code is not valid, actually it produces a compiling error in method GetShapes(). Solution is to explicitly cast rectanges to shapes.
return new ReadOnlyCollection<Shape>(rectangels.Cast<Shape>());
But this is O(n) operation and we would like O(1).
So .NET creators bring interface IReadOnlyList<out T> in .NET 4.5. As you can see out T means it is covariant, so following code will do job as I wanted.
public interface Shape { } public class Rectangle : Shape { } public interface Geometry { IReadOnlyList<Shape> GetShapes(); } public class RectangleGeomerty : Geometry { List<Rectangle> rectangles; IReadOnlyList<Shape> GetShapes() { return rectangles; } }
Yeah, now seems like everything is solved. But then you are looking at specification of .NET 4.5 and find out that List<T> implements IReadOnlyList<T> which is OK, but IList<T> doesn't implement IReadOnlyList<out T>, which is not OK.
So code like this is not possible:
IList<Rectangle> rectangles; IReadOnlyList<Shapes> shapes = rectangles;
In fact, interface IList<T> is not covariant so there is a problem and they are unable to change IList<T> to IList<out T> because it would require moving some members from IList<T> to IReadOnlyCollection<out T> and this would bring a lot of problems in CLR 4.5. In fact, CLR 4.5 would be unable to run applications developed with frameworks .NET 1.0, .NET 2.0, .NET 3.0, .NET 3.5 and .NET 4.0
The problem can be overcome simply by implementing class that inherits IReadOnlyList<T> and is wrapper for IList<T> object,
public class ReadOnlyList<T> : IReadOnlyList<T> { private IList<T> list; public ReadOnlyList(IList<T> list) { this.list = list; } public T this[int index] { get { return list[index]; } } public int Count { get { return list.Count; } } public IEnumerator<T> GetEnumerator() { return list.GetEnumerator(); } }
or we can add extension method for IList<T>
public static IReadOnlyList<T> AsReadOnly<T>(this IList<T> collection) { return new ReadOnlyList<T>(collection); }











