Grzenio
Grzenio

Reputation: 36649

Polymorphic keys to a Dictionary

I have a hierarchy of classes:

public class Key
{
    private readonly string _name;

    public Key(string name)
    {
        _name = name;
    }

    public string Name
    {
        get { return _name; }
    }

    public override bool Equals(object obj)
    {
        if (obj == null) return false;
        if (GetType() != obj.GetType()) return false;
        Key other = (Key)obj;
        return Name == other.Name;
    }

    public override int GetHashCode()
    {
        return GetType().GetHashCode() ^ _name.GetHashCode();
    }

    public override string ToString()
    {
        return GetType() + "(" + Name + ")";
    }
}

Different keys:

public class CarKey : Key
{
    public CarKey(string name)
        : base(name)
    {
    }
}

public class VanKey : CarKey
{
    public VanKey(string name)
        : base(name)
    {
    }
}

public class CoupeKey : CarKey
{
    public CoupeKey(string name)
        : base(name)
    {
    }
}

and I have a dictionary IDictionary<Key, Data>. I would like to implement a method to find the most relevant data in the dictionary:

IDictionary<Key, Data> dict;
public Data MostRelevantData(Key key)
{
  if(dict.Contains(key)) return dict[key];
  //try base class of key (with the same name) recursively
  throw new KeyNotFound();
}

Is it possible to implement MostRelevantData without reflection? Feel free to redesign the Key classes if it makes it any easier.

EDIT: Example:

dict[new Key("A")] = "Data A";
dict[new CarKey("B")] = "Data B";
dict[new CoupeKey("B")] = "Data B1";
dict[new CoupeKey("C")] = "Data C";

MostRelevantData(new CoupeKey("B"));//returns "Data B1"
MostRelevantData(new CoupeKey("A"));//returns "Data A"
MostRelevantData(new CoupeKey("C"));//returns "Data C"

MostRelevantData(new CarKey("C"));//throws
MostRelevantData(new CarKey("B"));//returns "Data B"

MostRelevantData(new VanKey("C"));//throws
MostRelevantData(new VanKey("B"));//returns "Data B"

Upvotes: 0

Views: 770

Answers (2)

Edmund Schweppe
Edmund Schweppe

Reputation: 5132

It is quite easy to implement MostRelevantData without reflection. Here's an extension method:

public static class DictionaryExtensionMethods
{
    public static T MostRelevantData<T>(this IDictionary<Key, T> dict, Key key)
    {
        if (dict.ContainsKey(key))
        {
            return dict[key];
        }
        Key relevantKey = dict.Keys.FirstOrDefault(
            loop => key.IsRelevant(loop));
        if (relevantKey != null)
        {
            return dict[relevantKey];
        }
        throw new KeyNotFoundException();
    }
}

Obviously, though, you'll need to implement Key.IsRelevant for this to work. Fortunately, that's pretty straightforward as well, courtesy of the Type.IsSubclassOf method:

public class Key
{
    public bool IsRelevant(Key other)
    {
        return (this.GetType().IsSubclassOf(other.GetType()))
            && (this.Name == other.Name);
    }
}

With the above implementation, the following Visual Studio unit tests all pass:

public class PolymorphicKeysTests
{
    public PolymorphicKeysTests()
    {
    }

    private Dictionary<Key, string> dict;
    [TestInitialize]
    public void TestInitialize()
    {
        dict = new Dictionary<Key, string>();
        dict[new Key("A")] = "Data A";
        dict[new CarKey("B")] = "Data B";
        dict[new CoupeKey("B")] = "Data B1";
        dict[new CoupeKey("C")] = "Data C";
    }

    [TestMethod]
    public void CoupeKeyB()
    {
        Assert.AreEqual("Data B1", dict.MostRelevantData(new CoupeKey("B")));
    }
    [TestMethod]
    public void CoupeKeyA()
    {
        Assert.AreEqual("Data A", dict.MostRelevantData(new CoupeKey("A")));
    }
    [TestMethod]
    public void CoupeKeyC()
    {
        Assert.AreEqual("Data C", dict.MostRelevantData(new CoupeKey("C")));
    }
    [TestMethod]
    [ExpectedException(typeof(KeyNotFoundException))]
    public void CarKeyC()
    {
        dict.MostRelevantData(new CarKey("C"));
    }
    [TestMethod]
    public void CarKeyB()
    {
        Assert.AreEqual("Data B", dict.MostRelevantData(new CarKey("B")));
    }
    [TestMethod]
    [ExpectedException(typeof(KeyNotFoundException))]
    public void VanKeyC()
    {
        dict.MostRelevantData(new VanKey("C"));
    }
    [TestMethod]
    public void VanKeyB()
    {
        Assert.AreEqual("Data B", dict.MostRelevantData(new VanKey("B")));
    }
}

Upvotes: 0

Lee
Lee

Reputation: 144136

You could add a method to Key to get a base key instance:

public class Key
{
    public virtual Key GetBaseKey()
    { return null; }
}

public class CarKey : Key
{
    public override Key GetBaseKey()
    { return new Key(this.Name); }
}

then your search can be written as:

public Data MostRelevantData(Key key)
{
  while(key != null)
  {
    if(dict.Contains(key)) return dict[key];
    key = key.GetBaseKey();
  }
  throw new KeyNotFound();
}

The downside is you'll have to effectively duplicate the key hierarchy in each subclass.

Upvotes: 3

Related Questions