views:

699

answers:

4

I'd like to expose a property on a view model that contains a list of objects (from database).

I need this collection to be read-only. That is, I want to prevent Add/Remove, etc. But allow the foreach and indexers to work. My intent is to declare a private field holding the editable collection and reference it with a read-only Public Property. As follows

public ObservableCollection<foo> CollectionOfFoo { 
     get { 
         return _CollectionOfFoo;
     }
}

However, that syntax just prevents changing the reference to the collection. It doesn't prevent add/remove, etc.

What is the right way to accomplish this?

A: 

You could change the type of your property to be an IEnumerable:

public IEnumerable<foo> CollectionOfFoo { 
     get { 
         return _CollectionOfFoo;
     }
}

I don't believe there is a standard interface that exposes an indexer. If you need it you could write an interface and extend ObservableCollection to implement it:

public interface IIndexerCollection<T> : IEnumerable<T>
{
    T this[int i]
    {
        get;
    }
}

public class IndexCollection<T> : ObservableCollection<T>, IIndexerCollection<T>
{
}
Jake Pearson
Remember, though, if CollectionOfFoo is say, a List<foo>, and the caller knows this, it can *cast* CollectionOfFoo back to List<foo>, and do add/remove operations. This works, but its not bullet-proof.
Charlie Salts
A: 

You could also override the list class that you're using and put an immutable flag in one of the constructors such that it will not add/remove if it was constructed with the immutable flag set to true.

JERiv
+2  A: 

Use ReadOnlyObservableCollection< T >

public ReadOnlyObservableCollection<T> ReadOnlyFoo
{
    get { return new ReadOnlyObservableCollection<T> (_CollectionOfFoo); }
}
scwagner
Thankyou, that's the ticket. There are sooooo many objects to choose from in the framework how does one discover them all!Thanks.
thrag
Note thought that INotifyCollectionChanged changed of ReadOnlyObservableCollection<T> is protected, and thus to access the event hookup you need to cast explicitly to INotifyCollectionChanged. An alternative would be to extend the ROOC to expose that event publicly (which it should have in the first place imho) and file a MSDN bug report on the current implementation. :)
GaussZ
@GaussZ Yes nice catch, why is it not public? It makes no sense at all to me. I might extract this into a new SO question to see if anyone else has some insight on this.
Oskar
As @Eric J. has pointed out, returning a new ReadOnlyObservableCollection every time the property is accessed makes it difficult for clients to correctly unsubscribe from events (which is presumably why it's a ReadOnlyObservableCollection, and not just a ReadOnlyCollection).
Bradley Grainger
+7  A: 

The accepted answer will actually return a different ReadOnlyObservableCollection every time ReadOnlyFoo is accessed. This is wasteful and can lead to subtle bugs.

A preferable solution is:

public class Source
{
    Source()
    {
        m_collection = new ObservableCollection<int>();
        m_collectionReadOnly = new ReadOnlyObservableCollection<int>(m_collection);
    }

    public ReadOnlyObservableCollection<int> Items
    {
        get { return m_collectionReadOnly; }
    }

    readonly ObservableCollection<int> m_collection;
    readonly ReadOnlyObservableCollection<int> m_collectionReadOnly;
}

See ReadOnlyObservableCollection anti-pattern for a full discussion.

Eric J.