A ReadOnlyCollection isn't readonly. How do i create a true ReadOnlyCollection?

1.4k Views Asked by At

I'm aware that readonly collection prevents adding/removing from a list but why doesn't it prevent the setting of properties of objects in the collection.

 System.Collections.ObjectModel.ReadOnlyCollection<PersonPhoneNumber> ReadOnlyPhoneNumbers = new System.Collections.ObjectModel.ReadOnlyCollection<PersonPhoneNumber>(_PhoneNumbers);
            ReadOnlyPhoneNumbers[0].Number = "01111111111111";

For the purpose of this question assume the _PhoneNumbers is a List and it contains at least one instance of the PersonPhoneNumber class.

How do expose a collection of objects and make the objects read only? The origins of this problem stem from having to expose a private collection in a WCF data contract but i don't want the collection to be accessible.

I want to use:

  Person.Mobile = "011111111111111";

Instead of:

Person.PhoneNumbers.Add(New PersonPhoneNumber{Number= "01111111111111", Type=Mobile});
7

There are 7 best solutions below

0
On BEST ANSWER

Since PersonPhoneNumber is a class, the ReadOnlyCollection provides you with access to the reference of any PersonPhoneNumber object. As you have the reference to the PersonPhoneNumber object, you can do anything that the PersonPhoneNumber object allows you to do and the fact that it came from a ReadOnlyCollection is not relevant.

You could consider making PersonPhoneNumber a struct instead. If you do that then when you request the indexed object from the collection the framework will box it in order avoid copying the entire struct (for efficiency). But if you then try to modify it you will get the error "Cannot modify the result of an unboxing conversion":

ReadOnlyPhoneNumbers[0].Number = "01111111111111";
// Cannot modify the result of an unboxing conversion

You can assign it to a local variable, but this will make a copy:

PersonPhoneNumber ppn = ReadOnlyPhoneNumbers[0].Number;
ppn.Number = "01111111111111";

ppn is a copy. The original in the ReadOnlyCollection is unmodified.

(Of course, before changing PersonPhoneNumber to a struct you'll need to consider the other consequences of this change. There may be other reasons that make a struct inappropriate.)

0
On

You cannot make the objects readonly. That would require altering the types. If you really need to provide a list of readonly-objects, you will need to create some sort of immutable wrapper type for each type that you want to expose (or make your types immutable from the start). Of course, this will be a separate type, if you need them to interoperate it will be rather complicated...

1
On

How could it prevent that? How would you try to build such a thing in C#? It could potentially copy anything that was returned from the indexer (etc) if it supported cloning - but then the object would need to perform the cloning. What would you expect to happen with the code you've just given? Should it throw an exception? If so, that's making a change to the PersonPhoneNumber class itself.

It sounds like you should basically change your PersonPhoneNumber class to be immutable. If you need a way of building it in a mutable fashion, you can create a builder type which knows how to create a PersonPhoneNumber - for example:

var ppn = new PersonPhoneNumber.Builder { /* set properties here }.Build();

For reasonably simple types it's easier to just write an appropriate constructor though.

0
On

The problem is making the objects read only as they go in - in order to make those objects read only you'll need to wrap them in some kind of proxy that doesn't allow values to be set.

Castle's dynamic proxy may allow you to wrap the objects in a ReadOnlyProxy of your own implementation. Then your add method would have to wrap everything in a ReadOnlyProxy as they went into the collection.

Not nice...

0
On

What you desire is not a question of the collection. The collection holds a reference to your items and you get this items by accessing the collection.
You can try to clone your objects before inserting. This does not prevent them from beeing changed but the original object will be preserved. Otherwise, you have to write a read-only wrapper or choose another construction for your objects.

1
On

You might want to look at this article.

C# Generics Recipes—Making Read-Only Collections the Generic Way

I think what you want is to keep the phonenumbers list a true private data member but then expose access to it via setters and getters on the person object for the various types of phone numbers. This would work just fine if the person can only have one of each type... otherwise you would have to change it to be a method. like .AddMobile("011111")

0
On

How about this:

  1. make an interface of your PersonPhoneNumber that only exposes read only properties (without setters)
  2. instead of a collection of PersonPhoneNumber, you create a read only collection of IPersonPhoneNumber but putting your PersonPhoneNumber items in it

Of course this doesn't prevent code from casting your items to PersonPhoneNumber but it does prevent accidental modifying of properties on items