Question

I'd like to have a class "A" with a (for example) SortedList collection "SrtdLst" property, and inside this class "A" allow the addition or subtraction of "SrtdLst" items. But in a instance of the class "A", only allow to get or set the content of the items, not to add new items or subtract the existing ones. In code:

class A
{
    public SortedList<string, string> SrtdLst = new SortedList<string, string>();

    public A()
    {
        // This must work:
        SrtdLst.Add("KeyA", "ValueA");
        // This too:
        SrtdLst["KeyA"] = "ValueAAA";
    }
}

class B
{
    public A a = new A();
    public B()
    {
        // I want the following code to fail:
        a.SrtdLst.Add("KeyB", "ValueB");
        // But this must work:
        a.SrtdLst["KeyA"] = "ValueBBB";
   }
}

UPDATE: I want to create a class like System.Data.SqlClient.SqlCommand. For the Stored Procedures you can use the member "DeriveParameters" that fills a collection of "Parameters", so only the value of each item can be modified.

How can this be done?

Was it helpful?

Solution

If you want to ban the modifying operations at compile time, you need a type-safe solution.

Declare an interface for the publicly allowed operations. Use that interface as the property type.

public interface IReadOnlyList<T>
{
    T this[int index] { get; }

    int Count { get; }
}

Then declare a class that implements that interface and inherits from the standard collection class.

public class SafeList<T> : List<T>, IReadOnlyList<T> { }

Assuming you get the interface definition right, you won't need to implement anything by hand, as the base class already provides the implementations.

Use that derived class as the type of the field that stores the property value.

public class A
{
    private SafeList<string> _list = new SafeList<string>();

    public IReadOnlyList<string>
    {
        get { return _list; }
    }
}

Within class A, you can use _list directly, and so modify the contents. Clients of class A will only be able to use the subset of operations available via IReadOnlyList<T>.

For your example, you're using SortedList instead of List, so the interface probably needs to be

public interface IReadOnlyDictionary<K, V> : IEnumerable<KeyValuePair<K, V>>
{
    V this[K index] { get; }        
}

I've made it inherit IEnumerable as well, which is readonly anyway, so is perfectly safe. The safe class would then be:

public class SafeSortedList<K, V> : SortedList<K, V>, IReadOnlyDictionary<K, V> { }

But otherwise it's the same idea.

Update: just noticed that (for some reason I can't fathom) you don't want to ban modifying operations - you just want to ban SOME modifying operations. Very strange, but it's still the same solution. Whatever operations you want to allow, "open them up" in the interface:

public interface IReadOnlyDictionary<K, V> : IEnumerable<KeyValuePair<K, V>>
{
    V this[K index] { get; set; }        
}

Of course, that's the wrong name for the interface now... why on earth would you want to ban adding via Add but not ban it via the indexer? (The indexer can be used to add items, just as the Add method can.)

Update

From your comment I think you mean that you want to allow assignment to the value of an existing key/value pair, but disallow assignment to a previously unknown key. Obviously as keys are specified at runtime by strings, there's no way to catch that at compile time. So you may as well go for runtime checking:

public class FixedSizeDictionaryWrapper<TKey, TValue> : IDictionary<TKey, TValue>
{
    IDictionary<TKey, TValue> _realDictionary;

    public FixedSizeDictionaryWrapper(IDictionary<TKey, TValue> realDictionary)
    {
        _realDictionary = realDictionary;
    }

    public TValue this[TKey key]
    {
        get { return _realDictionary[key]; }

        set 
        {
            if (!_realDictionary.Contains(key))
                throw new InvalidOperationException();

            _realDictionary[key] = value;
        }
    }

    // Implement Add so it always throws InvalidOperationException

    // implement all other dictionary methods to forward onto _realDictionary
}

Any time you have an ordinary dictionary and you want to hand it to some method that you don't trust to update the existing values, wrap it in one of these.

OTHER TIPS

EDIT: Original answer is below. As earwicker points out, I hadn't noticed that you aren't asking for it to be readonly - just to prevent the Add operation. That doesn't sound like a good idea to me, as the only difference between Add and the indexer-setter is that Add throws an exception if the element is already present. That could easily be faked up by the caller anyway.

Why do you want to restrict just that one operation?


Original answer

For one thing, don't use public fields. That's a surefire way to run into problems.

It looks like you want a read-only wrapper class round an arbitrary IDictionary. You can then have a public property which returns the wrapper, while you access the private variable from within your class. For example:

class A
{
    private SortedList<string, string> sortedList = new SortedList<string, string>();

    public IDictionary<string, string> SortedList 
    {
        get { return new ReadOnlyDictionaryWrapper(sortedList);
    }

    public A()
    {
        sortedList.Add("KeyA", "ValueA");
        sortedList["KeyA"] = "ValueAAA";
    }
}

Now you've just got to find a ReadOnlyDictionary implementation... I can't implement it right now, but I'll be back later if necessary...

Just make the list private, and expose it as an indexer:

class A {

   private SortedList<string, string> _list;

   public A() {
      _list = new SortedList<string, string>()
   }

   public string this[string key] {
      get {
         return _list[key];
      }
      set {
         _list[key] = value;
      }
   }

}

Now you can only access the items using the index:

a["KeyA"] = "ValueBBB";

However, as the indexer of the list allows creation of new items, you would have to add code in the indexer to prevent that if you don't want that do be possible.

If the keys are known outside of the class then you can add a ChangeItem(key, newValue) and ReadItem(key) to your wrapper class. Then keep the SortedList private to the class.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top