1

I'm converting a back-end to c# and I noticed a weird behavior

when I add an element to a list in a dictionary

(inside another dictionary actually):

shortly it adds the value to EVERY list in EVERY element in the dictionary.

Here's the code:

       public class Validator_Counter_Model
        {
            public readonly  Dictionary<string,Info_Model> Info;
            private Dictionary<string,Dictionary<DateTime,List<int>>> _map = new Dictionary<string, Dictionary<DateTime,List<int>>>();
            public Validator_Counter_Model(Dictionary<string, Info_Model> info)
            {
                Info = info;
            }
            public void Add(Validation_Element element)
            {

/// #### PROBLEM IS HERE!
                if (!_map.ContainsKey(element.Id))
                {
                    Dictionary<DateTime, List<int>> newmap = new Dictionary<DateTime, List<int>>();
                    _map.Add(element.Id, newmap);
                }

                DateTime fulldate = new Custom_Time_Model(element.Time).RegularTime;
                if (!_map[element.Id].ContainsKey(fulldate.Date))
                {
                    List<int> newList = new List<int>();
                    _map[element.Id].Add(fulldate.Date, newList);
                }
                _map[element.Id][fulldate.Date].Add(element.Time);
/// #### PROBLEM IS HERE!

            }

            public void Del(Validation_Element element)
            {
                DateTime fulldate = new Custom_Time_Model(element.Time).RegularTime;
                DateTime date = new DateTime(fulldate.Year, fulldate.Month, fulldate.Day);
                _map[element.Id][date].Remove(element.Time);
            }

            public void Update(Dictionary<string, Dictionary<DateTime, List<int>>> newMap) => _map = newMap;

            public Dictionary<string, Dictionary<DateTime, List<int>>> map => _map;

        }
    }
sajadre
  • 1,141
  • 2
  • 15
  • 30
Francesco Iapicca
  • 2,618
  • 5
  • 40
  • 85
  • 1
    If the code for `Info_Model`, `Validation_Element`, and `Customer_Time_Model` were included it would be possible to write a simple unit test to verify how this should behave. I see where you're adding a new `Dictionary` to `_map`, and where you're adding a new value to that dictionary. There are distinct dictionaries and `Add` doesn't add to more than one. So it makes me question whether the value really is getting added to every list in every element, or does something else in your code make it look like that's happening. – Scott Hannen May 14 '19 at 15:32
  • I'm not sure if that's your problem but the inner most List can have repeating values... if you call Validator_Counter_Model.Add for the same element multiple times. – Ventsyslav Raikov May 14 '19 at 15:35
  • sorry, custom time is just an int kinda like unix time validation_element is just a wrap for time (int) and id (string)... i still think "strongly typed" – Francesco Iapicca May 14 '19 at 17:36
  • If you able to make your classes more clear then maybe we can solve it without unit testing – sajadre May 14 '19 at 18:25

2 Answers2

1

found the answer HERE

basically the second dictionary (the one in the value of the first) is't a "dictionary" itself, but a reference of another dictionary that's why adding a value to it's list changes all the list for each key

Francesco Iapicca
  • 2,618
  • 5
  • 40
  • 85
0

Given that the dictionary keys are known types like string and DateTime, I can fill in implementations of the missing classes and the specifics don't really matter. I don't care if they contain meaningful values. I just want to see how everything behaves. Those are posted at the end:

Now it's possible to write a unit test to see if what you're describing is happening.

[TestMethod]
public void validation_elements_are_added_to_only_one_dictionary()
{
    var elementOne = new Validation_Element {Id = "A", Time = 1 };
    var elementTwo = new Validation_Element { Id = "B" , Time = 2};
    var elementThree = new Validation_Element { Id = "A" , Time = 3};

    var subject = new Validator_Counter_Model(new Dictionary<string, Info_Model>());
    subject.Add(elementOne);
    subject.Add(elementTwo);
    subject.Add(elementThree);

    var output = subject.map;

    var elementsWithId_A = output["A"];
    var elementsWithId_B = output["B"];

    var id_a_innerList = elementsWithId_A[new DateTime(2000, 1, 1)];
    var id_b_innerList = elementsWithId_B[new DateTime(2000, 1, 1)];

    Assert.AreEqual(2, id_a_innerList.Count);
    Assert.AreEqual(1, id_b_innerList.Count);
}

I'm adding two validation elements with Id "A" and one with Id "B". That means _map should contain two dictionaries. The key to the inner dictionary will always be 1/1/2000 so there will be two inner lists.

What I expect is that the inner list for A will have two items and the inner list for B will have one. If every item gets added to every list then they will both have two.

The test passes, which in this case was expected. Just from looking at the code we can see that Add is creating distinct lists and won't add an item to two lists. But now the test confirms that.

What that means is that if you are seeing items get added to multiple lists, the problem isn't where you thought it was. It's got to be somewhere else. That's one of the awesome things about unit tests. They give us a degree of certainty around one area of code so that if something isn't working we know that the problem is likely in the area that doesn't have unit tests. Narrowing down that search makes finding bugs faster and easier.

If we do find a problem with our unit test, then we can debug just that unit test instead of debugging the whole application. And then imagine if the other classes had unit tests, too. We're always going to make mistakes as we code, but with unit tests we just find them faster and fix them more easily.

In this case it's a little bit messy that I've made up fake implementations of your classes. Even though I don't think it makes a difference since they're just data, it does introduce a tiny degree of uncertainty. I'd replace them with the real ones and then adjust the test accordingly. And you can write more tests as needed.


Working implementations of the classes that weren't provided:

public class Validation_Element
{
    public string Id { get; set; }
    public int Time { get; set; }
}

public class Custom_Time_Model
{
    public Custom_Time_Model(int time)
    {
        Time = time;
        // This makes it possible to create classes where `Time` is different
        // but fullDate.Date (in the Add method) is the same. If I want that date
        // to be different I'll add 24 or more hours.
        RegularTime = new DateTime(2000,1,1).AddHours(time);
    }
    public DateTime RegularTime { get; set; }
    public int Time { get; set; }
}

public class Info_Model { }
Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
  • so long story short, the shown code isn't "wrong", isn't it? – Francesco Iapicca May 14 '19 at 17:44
  • I don't know all the details of what it's supposed to do, so I can't say whether it's right or wrong. But it's safe to say that calling `Add` isn't adding an item to multiple lists. One concern is the `map` property which exposes the entire dictionary. This class might not be adding anything incorrectly, but it's possible for another class to access the dictionary and add or change its contents. It would be good if other classes had ways to read what they need, but if the only way to change the contents of those lists and dictionaries was with the `Add` method. – Scott Hannen May 14 '19 at 17:48