views:

1698

answers:

4

Hello All,

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?

+6  A: 

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...

Jon Skeet
Damn you and your omnipresence skeet :P
annakata
I know! Get a job Skeet! :P
jcollum
I don't like detecting errors at runtime, hence my answer uses the static type system instead. But the other problem (I didn't notice this at first either!) is that the OP isn't asking for it to be readonly. They just want to ban the Add method, and yet still allow assignment-via-indexer (which can also add items). Very odd...
Daniel Earwicker
Ooh - I hadn't noticed that it would have to be just readonly in terms of Add. Not good. Will edit.
Jon Skeet
I want to create a class like System.Data.SqlClient.SqlCommand for the Stored Procedures, with a member like "DeriveParameters" an a semi-read-only "Parameters" property. And use it to print, show, and save SQL Server Reports. Still thinking it's so odd??
Alex
Do you know the namespace for ReadOnlyDictionaryWrapper, because I can't find it...
Alex
@Alex: There isn't one in the framework - you'd have to write your own implementation.
Jon Skeet
+6  A: 

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.

Daniel Earwicker
I want to ban all, but I thought to ban the indexer to add items via setters, but i didn't know how to ban the addition by the method "Add". As I said in my prior comment to Skeet, what I want to create is a class like System.Data.SqlClient.SqlCommand for the Stored Procedures, with a member like "DeriveParameters" that fills a collection of "Parameters", so only the value of each item can be modified.
Alex
See further update.
Daniel Earwicker
A: 

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.

jcollum
+2  A: 

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.

Guffa