collection of key-value pairs where key depends on value

Viewed 1151

I have a class MyClass

class MyClass
{
    public string Name { get; set; } // is unique among all instances
    public SomeClass Data { get; set; }
    ...
}

of which I want to store several instances in a collection. I will often need to check if an instance with a certain name exists, and if it does, retrieve that instance. Since iterating through the whole collection is not an option (performance!), I thought of a using a collection of key-value-pairs, e.g. an IDictionary<string, MyClass>.

My program will also allow renaming instances of MyClass (it will not allow renaming if name uniqueness would be violated). But if I rename a MyClass, I will also need to delete the old entry from the dictionary and add the new one (i.e. with the new name) to keep the data consistent.

The problem is that I will have several such dictionaries (which contain subsets of all MyClass instances) all over the place, and it will be hard to keep track of them and consistently update all dictionaries after every renaming.

Is there a way to keep the key-value pairs consistent automatically? I think I heard of a data structure that allows this, which exists at least in C++ (unfortunately, I have no idea how it's called). Basically, it should be a collection where the key is not just a plain string but more like a reference to a string (to the name property in this case), but behaves as if it were a string. Does such a thing exist in C#? Do you have other ideas how to keep the collections consistent?

My only idea is to have a collection of all dictionaries on the highest level of my program and to make the renaming method update all those dictionaries after the actual renaming process. But there must be a better way!


Why this question is not a duplicate of Best way to change dictionary key :

I already know that a dictionary does not allow changing the key. I am instead asking for another data structure that is somehow compatible to key changes (without losing the performance benefit completely), and I am asking for other approaches as well. So my question is much more open to input from any direction, as long as it helps to solve the problem of keeping the data consistent.

2 Answers

As far as I understand you, your problem is this:

  • You have multiple dictionaries, each holds a part of your data
  • All your instances should have a unique name throughout all the dictionaries
  • When a name is changed:
    • First, check this name is still unique
    • Update it in whatever dictionray it lives in

I think I would tackle this problem a bit differently.

First, add an ID field to the class that would be a Guid / running number, That field should never change from the moment the instance is created.
Next, add another dictionary that would hold only the names and IDs of the instances, it should look something like this:

[{"FirstName": "Guid1"},   
{"SecondName": "Guid2"},   
{"ThirdName": "Guid3"}]  

The rest of your dictionaries will hold the ID as their key, and not the name:

[{"Guid1": {instance1}},  
{"Guid2": {instance2}}] 

Now when you change the name of an instance, all the names exist in a single dictionary that'll tell you if it already exist. And you need to change it only in the single place, since the rest of the dictionaries rely on a constant value that'll never change.
So say you want to change the name of "FirstName", the names dictionary will look like this:

[{"OtherName": "Guid1"},   
{"SecondName": "Guid2"},   
{"ThirdName": "Guid3"}] 

And the rest of the data doesn't need to change.

I don't think there is a native collection to do that. However you can easily create your own by just adding some kind of notification in your base class.

   public class ChangingNameObject
    {
        public delegate void ObjectNameChange(string oldName, string newName);
        public event ObjectNameChange ObjectNameChanged;
        private string name;
        public string Name 
        { 
            get => name;
            set
            {
                ObjectNameChanged?.Invoke(name, value);
                name = value;
            }
        }
    }

    public class WatchingDictionary
    {
        private Dictionary<string, ChangingNameObject> content = new Dictionary<string, ChangingNameObject>();

        public void Add(ChangingNameObject item)
        {
            item.ObjectNameChanged += UpdatePosition;
            content[item.Name] = item;
        }

        public void Remove(ChangingNameObject item)
        {
            item.ObjectNameChanged -= UpdatePosition;
            content.Remove(item.Name);
        }

        private void UpdatePosition(string oldname, string newname)
        {
            var o = content[oldname];
            content.Remove(oldname);
            content.Add(newname, o);
        }
    }

I only wrote the very basic stuff and you miss all the accessors and enumerators, just add the one you need.

Be very carefull with enumeration though as changing a collection during enumeration will cause a fail (and as the collection is hidden, youu might be doing it without knowing)

Related