简体   繁体   English

C#字典意外的参考行为

[英]C# Dictionary unexpected reference behaviour

Background 背景

I understand a C# dictionary is a data structure that represents a collection of key|value pairs. 我了解C#词典是一种表示键对值集合的数据结构。

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. 以下代码(LINQPad C#程序)和随后的输出屏幕截图演示了我如何尝试使用字典来(1)按创建顺序保留类的最新两个实例( RefInteger ),以及(2)允许“最新RefInteger实例的“删除”。 Where : 哪里:

  • refDict[A] returns the most recent created instance of RefInteger , refDict[A]返回最近创建的实例RefInteger
  • refDict[B] returns the second most recent created instance, and refDict[B]返回第二个最近创建的实例,并且
  • refDict[C] is only required to "resupply" refDict[B] in the event of the most recent RefInteger instance is "deleted" (via the RegressEntries method). 仅当最新的RefInteger实例被“删除”(通过RegressEntries方法)时,才需要refDict[C] “重新提供” refDict[B]

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输出

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 refDict key pointing to the most recent value. 尽管上面的简化示例的行为与我期望的字典一样,但是我有一个字典,其中ProgressEntries方法似乎导致每个后续的refDict键指向最新值。 It would be the equivalent of receiving the below output from the simplified example: 这相当于从简化示例中接收以下输出:

在此处输入图片说明

Please suggest how this is possible. 请建议如何实现。

Noting, I have confirmed the order of "progression" within the ProgressEntries method is correct (ie move refDict[B] to refDict[C] THEN move refDict[A] to refDict[B] ). 注意,我已经确认ProgressEntries方法中“进度”的顺序是正确的(即,将refDict[B]移至refDict[C]然后将refDict[A]移至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. 该示例代码使用一个简单的类( refInteger )而不是原始类型,以确保我对字典行为的理解是正确的。

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;
            }
        }            

    }
}

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 . 正如评论员Jon Skeet所建议的那样,如果您想获得无效代码的帮助,则您在问题中发布的代码应该是该代码 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. 我敢打赌,在无效的代码中,您不是在分配new ...something...作为值,而是在分配对其他对象的引用。

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. 因此,如果以上内容无法为您解释为什么无法正常工作的代码行为不正确,则应编辑问题以包含正确的代码示例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM