tags:

views:

73

answers:

3

I cannot find a dictionary entry by key. I have an interface like the following:

public interface IFieldLookup
{
    string FileName { get; set; }
    string FieldName { get; set; }
}

Then I have a dictionary like so:

Dictionary<IFieldLookup, IField> fd

When I try to retrieve an element out of the dictionary by the key, I get a KeyNotFoundException. I am assuming that I have to implement some type of comparison - if my assumption is correct, what is the recommended way of implementing a comparison in this case?

+4  A: 

use ContainsKey and override equals on the key class

Ok lets say this is our key class:

class Key
{
  public int KeyValue;
  public override Equals(object o)
  {
    return ((Key)o).KeyValue == KeyValue);
  }
}

now lets use the class as a key

Dictonary<Key, string> dict = new Dictonary<Key, string>();
Key k = new Key();
k.KeyValue = 123;
dict.Add(k, "Save me!");
Key k2 = new Key();
k2.KeyValue = 123;
if (dict.ContainsKey(k2))
{
  string value = dict[k2];
}
Petoj
It's the "override equals" part that's important here.
Joel Coehoorn
@Petoj - you say to use the ContainsKey - can you explain? Are you recommending it so that I don't get the exception? If that is the case, I would like an exception in this situation.
thedugas
-1: Use `IEqualityComparer<T>` instead.
280Z28
+1  A: 

Since this is an interface rather than a class, you will have to define your equality operator for every class that implements the interface. And those operators will need to operate consistantly. (This would be much better if it were a class rather than an interface.)

You must override the Equals(object) and GetHashCode() methods on each class.

Probably something like this:

public override bool Equals(object obj)
{
   IFieldLookup other = obj as IFieldLookup;
   if (other == null)
        return false;
   return other.FileName.Equals(this.FileName) && other.FieldName.Equals(this.FieldName);
}

public override int GetHashCode()
{
    return FileName.GetHashCode() + FieldName.GetHashCode();
}

or this:

public override bool Equals(object obj)
{
   IFieldLookup other = obj as IFieldLookup;
   if (other == null)
        return false;
   return other.FileName.Equals(this.FileName, StringComparison.InvariantCultureIgnoreCase) && other.FieldName.Equals(this.FieldName, StringComparison.InvariantCultureIgnoreCase);
}

public override int GetHashCode()
{
    return StringComparer.InvariantCulture.GetHashCode(FileName) +
           StringComparer.InvariantCulture.GetHashCode(FieldName);
}

Depending on how you want it to behave.

Jeffrey L Whitledge
-1: Since the comparison is specific to this dictionary, use the necessary `IEqualityComparer<T>` instead.
280Z28
@280Z28 - The OP does not state that the comparison will be specific to this dictionary. It is very likely that the comparison will have other uses within the program, and the semantics of those uses needs to be the same..
Jeffrey L Whitledge
I understand what both @Jeffrey and @280Z28 are saying. The excerpt from the MSDN page of IEquatable<T> summed it up nicely:The IEquatable<(Of <(T>)>) interface defines the equality on the type itself, whereas an IEqualityComparer<(Of <(T>)>) interface is external to the type and enables many different implementations to be used for the same type.Thank you both for your input.
thedugas
@Jeffrey - please edit your answer so that I can re-upvote you - I misclicked.
thedugas
@thedugas - Done. Thanks!
Jeffrey L Whitledge
+1  A: 

Implement an instance of IEqualityComparer<T> (recommended by deriving from EqualityComparer<T> for its automatic implementation of IEqualityComparer as well) for the key type, and pass an instance to the dictionary constructor. This way you can implement the comparison consistently across multiple implementations of the interface.

280Z28
-1 This runs the risk that inconsistant equality comparers will be used in operations that don't involve this specific dictionary.
Jeffrey L Whitledge
@280Z28 - after reading about these Interfaces, I see MSDN says that the EqualityComparer<T>).Default property:"checks whether type T implements the System.IEquatable<T> generic interface and, if so, returns an EqualityComparer<T>) that contains the implantation of the IEquitableEquals method. Otherwise, it returns an EqualityComparer<T>), as provided by T." So I am guessing that the appropriate thing to do is implement IEquatable<T> and use the EqualityComparer<T>.Default and in the case of needing a different comparison then derive from EqualityComparer<T>?
thedugas
For this dictionary, you would not use the default `EqualityComparer<T>` - that's what `Dictionary<T,K>` uses if you don't specify a custom comparer.
280Z28