views:

383

answers:

5

Consider the following code, where each key has an identical value:

IDictionary<string, string> quarterbackDictionary = new Dictionary<string, string>();
quarterbackDictionary.Add("Manning", "Manning");
quarterbackDictionary.Add("Brady", "Brady");
quarterbackDictionary.Add("Rivers", "Rivers");

My question:

  • Can I remove the redundancy, so that I don't have to repeat each string twice, similar to the following:
IDictionary<string, string> quarterbackDictionary = new Dictionary<string, string>();
quarterbackDictionary.Add("Manning");
quarterbackDictionary.Add("Brady");
quarterbackDictionary.Add("Rivers");

FYI:

  • I'm using a Dictionary because I want to throw on an attempt to insert a duplicate key.
  • A HashSet would not throw on an attempt to insert a duplicate key.
+11  A: 

You could wrap a HashSet<string> in your own class, and have it throw an exception if you try to add the same key twice.

It won't be much trouble to define that class, in fact, here is a possible implementation that you can tweak to fit your needs:

    public class UniqueHashSet<T> : ICollection<T>
    {
        private readonly HashSet<T> innerSet = new HashSet<T>();

        public void Add(T item)
        {
            if (innerSet.Contains(item))
                throw new ArgumentException("Element already exists", "item");
            innerSet.Add(item);
        }

        public void Clear()
        {
            innerSet.Clear();
        }

        public bool Contains(T item)
        {
            return innerSet.Contains(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            innerSet.CopyTo(array, arrayIndex);
        }

        public bool Remove(T item)
        {
            return innerSet.Remove(item);
        }

        public int Count
        {
            get { return innerSet.Count; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public IEnumerator<T> GetEnumerator()
        {
            return innerSet.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return innerSet.GetEnumerator();
        }
    }

As the other answers mention, you could also make it an extension method. I think you could certainly do this, unless you need to be absolutely sure that you cannot add the same item twice (if you do it the extension method way, one could still call the regular .Add method).

driis
-1: I'm using a Dictionary because I want to throw on an attempt to insert a duplicate key.
Jim G.
+1: It is a good answer.
dalle
Instead of using a Dictionary<> why don't you use a HashSet<> and add a function which throws an exception if the key already exists or adds the key to the hash?
Aaron
@Aaron: Exactly what I was thinking. If you NEED an exception thrown, use an (extension?) function where `if (Add == false) throw new Exception();`
Will Eddins
d'oh - you guys reached the same conclusion as me while I was typing mine in
Phil Nash
@driis: Thanks for the update. I removed the -1.
Jim G.
+1: Nice job, driis. Thanks.
Jim G.
+1  A: 

Inherit from System.Collections.ObjectModel.Collection and override InsertItem (which is protected).

Then you can do your duplicate check and throw when someone inserts a duplicate item. InsertItem is called on any of the methods that can put in a new item: Add, Insert, etc.

Kyralessa
The `System.Collections.ObjectModel.Collection` class is still an array-based collection; finding items in it using `.Contains()` will take linear time. The appeal of a dictionary is that lookup time is roughly constant.
CodeSavvyGeek
It would be interesting to see the app so heavily optimized that that actually makes a difference.
Kyralessa
+3  A: 

Add an extension method to HashSet, say AddUnique, which just calls Add and throws if the return is false.

Phil Nash
+1: This works too.
Jim G.
+1  A: 

Perhaps you could use an extension method

public static class DictionaryExtensions
{
    public static void Add(this Dictionary<string, string> dictionary,  
        string keyAndValue)
    {
        string value;
        if (dictionary.TryGetValue(keyAndValue, out value))
        {
            throw new Exception();
        }

        dictionary.Add(keyAndValue, keyAndValue);
    }
}
mrydengren
I think an extension method is the way to go, but I'd add it to HashSet instead.
Phil Nash
I would probably choose to add an extension method to HashSet or implement my own collection in the way driis did. But the topic starter seemed set on using a Dictionary<,> that's way I chose to use it for my answer.
mrydengren
The OP (Jim) stated, "'m using a Dictionary because I want to throw on an attempt to insert a duplicate key". If that's the only reason I think a HashSet would be a better fit.
Phil Nash
@Phil Nash: A Hashset *would* work too. Six of one or half-dozen of the other. I gave your answer a +1 too.
Jim G.
Thanks, @Jim G. However, I'd rate it as 5 of one 7 of the other ;-) If a HashSet is suitable then it has the advantage of less storage overhead (a Dictionary will store each item twice - once for the key once for the value). Conceptually that's twice the storage - although in practice it will be less due to the hashing and other overheads - but probably a good 1.5x
Phil Nash
+1  A: 

You could also inherit from System.Collections.ObjectModel.KeyedCollection.

class MyDictionary : KeyedCollection<string, string>
{
 protected override string GetKeyForItem(string item)
 {
  return item;
 }
}

var d = new MyDictionary();
d.Add("jones");
d.Add("jones");   // this will except
dkackman
+1 `KeyedCollection` can be used in .NET 2.0. `Hashset` is only available in .NET 3.5.
CodeSavvyGeek