shansen
shansen

Reputation: 375

C# Dictionary unexpected reference behaviour

Background

I understand a C# dictionary is a data structure that represents a collection of key|value pairs.

The below code (LINQPad C# Program) and subsequent output screenshot demonstrates how I am attempting to use a dictionary to (1) retain the most recent two instances of a class (RefInteger) in order of creation, and (2) allow for the "deletion" of the most recent RefInteger instance. Where :

Code:

public enum Pos { A, B, C }
public Dictionary<Pos, RefInteger> refDict = new Dictionary<Pos, RefInteger>(); 

void Main()
{
    Create(1);
    refDict.Dump("Create 1");
    Create(2);
    refDict.Dump("Create 2");
    Create(3);
    refDict.Dump("Create 3");
    RegressEntries();
    refDict.Dump("Regress");
    Create(4);  
    refDict.Dump("Create 4");
    Create(5);  
    refDict.Dump("Create 5");
}

private void Create(int value)
{
    ProgressEntries();
    refDict[Pos.A] = new RefInteger(value); 
}

private void ProgressEntries()
{
    if (refDict.ContainsKey(Pos.B))
        refDict[Pos.C] = refDict[Pos.B];
    if (refDict.ContainsKey(Pos.A))
        refDict[Pos.B] = refDict[Pos.A];
}

private void RegressEntries()
{
    if (refDict.ContainsKey(Pos.B))
        refDict[Pos.A] = refDict[Pos.B];
    if (refDict.ContainsKey(Pos.C))
        refDict[Pos.B] = refDict[Pos.C];
}

public class RefInteger
{
  public int Value;

  public RefInteger(int value)
  {
      Value = value;
  }
}

Screenshot

LINQPad Output

Question

While the above simplified example behaves as I expect a dictionary to behave, I have dictionary where the ProgressEntries method appears to result in each subsequent refDictkey pointing to the most recent value. It would be the equivalent of receiving the below output from the simplified example:

enter image description here

Please suggest how this is possible.

Noting, I have confirmed the order of "progression" within the ProgressEntries method is correct (i.e. move refDict[B] to refDict[C] THEN move refDict[A] to refDict[B]).
The problem dictionary while small, uses a composite key to return values. Moving to another structure is not really an option.
The example code uses a simple class (refInteger) as opposed to a primitive type to ensure my understanding of dictionary behavior is correct.

Thanks

Shannon

EDIT: Please find below the requested example console app code. The example behaves as expected. I will keep digging into the issue with the actual code.

using System;
using System.Collections.Generic;

namespace DictionaryProgress
{
    class Program
    {
        public enum Pos { A, B, C }

        static void Main()
        {
            var refDictionary = new RefDictionary();
            refDictionary.Update();
            Console.ReadKey();
        }

        public class RefDictionary
        {
            public Dictionary<Pos, RefInteger> RefDict = new Dictionary<Pos, RefInteger>();

            public void Update()
            {
                Create(1);
                Console.WriteLine("Create 1 : {0}", ToString());
                Create(2);
                Console.WriteLine("Create 2 : {0}", ToString());
                Create(3);
                Console.WriteLine("Create 3 : {0}", ToString());

                RegressEntries();
                Console.WriteLine("Regress  : {0}", ToString());
                Create(4);
                Console.WriteLine("Create 4 : {0}", ToString());
                Create(5);
                Console.WriteLine("Create 5 : {0}", ToString());
            }

            private void Create(int value)
            {
                ProgressEntries();
                RefDict[Pos.A] = new RefInteger(value);
            }

            private void ProgressEntries()
            {
                if (RefDict.ContainsKey(Pos.B))
                    RefDict[Pos.C] = RefDict[Pos.B];
                if (RefDict.ContainsKey(Pos.A))
                    RefDict[Pos.B] = RefDict[Pos.A];
            }

            private void RegressEntries()
            {
                if (RefDict.ContainsKey(Pos.B))
                    RefDict[Pos.A] = RefDict[Pos.B];
                if (RefDict.ContainsKey(Pos.C))
                    RefDict[Pos.B] = RefDict[Pos.C];
            }

            public override string ToString()
            {
                var PosA = RefDict.ContainsKey(Pos.A) ? 
                    RefDict[Pos.A].Value : 0;
                var PosB = RefDict.ContainsKey(Pos.B) ?
                    RefDict[Pos.B].Value : 0;
                var PosC = RefDict.ContainsKey(Pos.C) ? 
                    RefDict[Pos.C].Value : 0;

                return string.Format("{0}, {1}, {2}", PosA, PosB, PosC);
            }
        }

        public class RefInteger
        {
            public int Value;

            public RefInteger(int value)
            {
                Value = value;
            }
        }            

    }
}

Upvotes: 1

Views: 256

Answers (1)

Peter Duniho
Peter Duniho

Reputation: 70701

As commenter Jon Skeet has suggested, if you want help with code that doesn't work, the code you post in your question should be that code. Showing us code that does work, but then asking us to explain why some other code doesn't work isn't a very good way to get an answer.

That said, assuming the code that doesn't work is even remotely like the code that does work, I would say that the most obvious explanation for the behavior you are seeing is that the broken version of the code is not creating a new instance of the value object, but rather just reusing a previously-created instance.

That is, the described behavior shows a classic "reference-type copy" symptom.

Note that in the code that does work, you create a whole new instance of the value object when it's added to the dictionary:

RefDict[Pos.A] = new RefInteger(value);

I would bet that in the code that doesn't work, instead of assigning new ...something... as the value, you're assigning a reference to some other object.

Of course, without a good, minimal, complete code example showing the version of the code that doesn't work, it's not possible to know for sure what's wrong with it. So if the above doesn't explain for you why the code that doesn't work is behaving incorrectly, you should edit your question to include a proper code example.

Upvotes: 1

Related Questions