0

I'm working on an approach to have a collection that efficiently can search based on more than one property. The sample code of the approach:

class SampleCollection 
{
    Dictionary<Sample, Sample> _dictItems;

    public SampleCollection()
    {
        _dictItems = new Dictionary<Sample, Sample>(new SampleEqualityComparer());
    }

    public Sample FindById(int id)
    {
        return _dictItems[new Sample(id, string.Empty)];
    }

    public Sample FindByName(string name)
    {
        return _dictItems[new Sample(-1, name)];
    }
}

class Sample
{
    public Sample(int id, string name)
    {
        Id = id;
        Name = name;
    }

    public int Id { get; set; }
    public string Name { get; set; }

    public string ALotOfOtherProperties { get; set; }
}

class SampleEqualityComparer : IEqualityComparer<Sample>
{
    public bool Equals(Sample x, Sample y)
    {
        if (x.Id >= 0 && y.Id >= 0)
        {
            return x.Id == y.Id;
        }

        return x.Name.Equals(y.Name, StringComparison.CurrentCultureIgnoreCase);
    }

    public int GetHashCode(Sample obj)
    {
        //try with only name now
        return obj.Name.GetHashCode();
        //return 0;
    }
}

This approach works perfectly fine as long as the Name property is not modified. Understandably, the Hash value no longer matches the original item in the Dictionary when the Name is modified.

Is it possible to force the Dictionary to recalculate the hash of its keys or any other workaround if it is not possible directly.?

KDR
  • 478
  • 6
  • 19
  • 1
    Do not do that. Read [this](https://stackoverflow.com/a/7941876/4228458) – CodingYoshi Jul 05 '17 at 09:18
  • @CodingYoshi, For that reason, I wanted to know whether the hash values inside the Dictionary could be recalculated or any other workaround if it is not possible directly. – KDR Jul 05 '17 at 09:26
  • Why do you work with a `Dictionary` instead of a `Dictionary`? If you want to find it by both `Name` *and* `Id`, why not have two separate dictionaries (or [(I)Lookups](https://msdn.microsoft.com/library/bb460184.aspx))? The storing structure (dictionary/lookup) should not take too much additional memory, since the bulk of the data (presumably) is in `Sample` and you're only working with references. – Corak Jul 05 '17 at 09:28
  • There is no workaround and the key should not be recalculated. Did you read the link I provided? – CodingYoshi Jul 05 '17 at 09:30
  • Then you could have one structure to keep all the `Sample` items and when you know something changed, you could clear and refill the other structures. – Corak Jul 05 '17 at 09:30
  • @Corak, Clearing and refilling the collection is an expensive operation and adds too much book keeping code to be performed. string as key will not work since the string property is what is modified. One Solution would be subscribe to the property changed of Name property and refresh the collection on value change. – KDR Jul 05 '17 at 09:43
  • You could write a dictionary that can do this, but it would need cooperation from the items being modified. You can definitely not do it with the standard `Dictionary`, except by removing the item *before* it is modified and re-inserting it afterwards (which, again, requires cooperation from the items being modified). You could conceivably use notification properties and `INotifyPropertyChanged`. – Jeroen Mostert Jul 05 '17 at 09:53
  • 1
    1. get "old" item. 2. remove "old" item from dictionary 3. change name of item (now it's "new" item) 4. add new item to dictionary. – Corak Jul 05 '17 at 09:55

1 Answers1

1

It is really a performance hit when using a custom class as a key. The handling can take a 10 times longer.

I suggest that you have a dictionary for name and one for id.

I recommend that you set your Name and Id setter to private like:public string Name { get; private set; }

class SampleCollection
{
    public SampleCollection()
    {
          NameLookup = new Dictionary<string, List<Sample>>();
          IdLookup = new Dictionary<int, Sample>();
    }
    private Dictionary<string, List<Sample>> NameLookup;
    private Dictionary<int, Sample> IdLookup;
    public void Add(Sample sample)
    {
       IdLookup.Add(sample.Id, Sample);
       List<Sample> list;
       if (!NameLookup.TryGetValue(sample.Name, out list))
          NameLookup.Add(sample.Name, list = new List<Sample>());
       list.Add(Sample);
    }
    public Sample FindById(int id)
    {
       Sample result;
       IdLookup.TryGetValue(id, out result);
       return result;
    }
    public IEnumerable<Sample> FindByName(string name)
    {
       List<Sample> list;
       if (NameLookup.TryGetValue(name, out list))
         foreach(var sample in list)
           yield return sample;
    }
}
Casperah
  • 4,504
  • 1
  • 19
  • 13