为什么在 C# 中通过引用添加字典元素?

Why are dictionary elements being added by reference in C#?

我正在尝试做一些看起来应该很容易的事情,但它不起作用。我有一个带有 int 键的字典对象。在对象中,我有一个 属性,PositionInEvent,我想匹配其中的字典键。看起来这应该是一个简单的循环操作,但它不起作用。这是我拥有的:

private void ensurePositions(ref Dictionary<int, DisplayUnit>  dict)
{
    var keys = dict.Keys.ToArray();
    foreach(var key in keys)
    {
        dict[key].PositionInEvent = key;
    }
}

当我 运行 在一个包含 5 个对象的字典上使用 0-4 键时(它不会总是像这样连续的,但我正在对其进行单元测试),PositionInEvent 属性 事件中每个项目的值为 4。每一个。为什么????我怎样才能做我想做的事。看起来应该很简单。

更新:

有人要求我展示如何声明、实例化 DisplayUnit 并将其添加到字典中。

这是 class 声明(我已经删除了与实例化无关的内容以及我正在使用的 属性):

/// <summary>
/// This is the base display unit from which all other units are derived.
/// </summary>
public abstract class DisplayUnit
{

    /// <summary>
    /// Initializes a new instance of the <see cref="AbstractClasses.DisplayUnit"/> class.
    /// </summary>
    protected DisplayUnit (Dictionary<string,string> attributes)
    {
        this.Id = Guid.NewGuid();
        tryApplyAttributes(attributes);
    }

    protected DisplayUnit(Guid id, Dictionary<string,string> attributes)
    {
        this.Id = id;
        tryApplyAttributes(attributes);
    }

    private void tryApplyAttributes(Dictionary<string,string> attributes)
    {
        string name;
        attributes.TryGetValue("Name", out name);
        Name = name;

        string description;
        attributes.TryGetValue("Description", out description);
        Description = description;

        string dateTime;
        attributes.TryGetValue ("DateCreated", out dateTime);
        DateTime date;
        DateTime.TryParse(dateTime,out date);
        DateCreated = date;

        string guid;
        attributes.TryGetValue("AssociatedEvent", out guid);
        Guid id;
        Guid.TryParse(guid, out id);
        AssociatedEvent = id;

        string group;
        attributes.TryGetValue("GroupId", out group);
        Guid groupId;
        var groupSet = Guid.TryParse(group, out groupId);

        string posInGroup;
        attributes.TryGetValue("PositionInGroup", out posInGroup);
        int intPos;
        var posSet = int.TryParse(posInGroup, out intPos);

        if (posSet && groupSet)
            UnitGroup = new DisplayUnitGrouping (intPos, groupId);

        string pos;
        attributes.TryGetValue("PositionInEvent", out pos);
        int position;
        int.TryParse (pos, out position);
        PositionInEvent = position;
    }

    public Guid Id {
        get;
        private set;
    }

    private int _positionInEvent;
    public int PositionInEvent {
        get{
            return _positionInEvent;
        }
        set {
            if (value < 0) {
                throw new NegativePositionException ("Position of DisplayUnit must be positive.");
            }
            _positionInEvent = value;
        }
    }

}

TextUnit 是我实际上 使用的 class,它派生自 DisplayUnit:

public class TextUnit : DisplayUnit
{
    public string Text {
        get;
        set;
    }

    public TextUnit (Dictionary<string, string> attributes) : base (attributes)
    {
        SetAttributes (attributes);
        Plugin = new FaithEngage.Plugins.DisplayUnits.TextUnitPlugin.TextUnitPlugin ();
    }


    public TextUnit (Guid id, Dictionary<string, string> attributes) : base (id, attributes)
    {
        SetAttributes (attributes);
    }


    #region implemented abstract members of DisplayUnit

    public override void SetAttributes (Dictionary<string, string> attributes)
    {
        string text;
        attributes.TryGetValue ("text", out text);
        Text = text;
    }

    #endregion
}

正在执行的字典来自这里。 _duRepo 是一个伪造的存储库(请参阅下面的代码)。

public Dictionary<int, DisplayUnit> GetByEvent(Guid eventId)
{
    try {
        var returnDict = new Dictionary<int,DisplayUnit>();
        var dict = _duRepo.GetByEvent(eventId);
     if (dict == null)
            return null;
        foreach(var key in dict.Keys)
        {
            var du = _factory.ConvertFromDto(dict [key]);
            if(du == null) continue;
            returnDict.Add (key, du);
        }
        ensurePositions(ref returnDict);
        return returnDict;
    } catch (RepositoryException ex) {
        throw new RepositoryException ("There was a problem accessing the DisplayUnitRepository", ex);
    }
 }

这一切都来自这个单元测试(我无法通过,我不知道为什么):

[Test]
public void GetByEvent_ValidEventId_ReturnsDictOfEvents()
{
    var dict = new Dictionary<int,DisplayUnitDTO>();
    for(var i = 0; i < 5; i++)
    {
        dict.Add(i, new DisplayUnitDTO());
    }
    var repo = A.Fake<IDisplayUnitsRepository>();
    A.CallTo(() => repo.GetByEvent(VALID_GUID)).Returns(dict);
    A.CallTo(() => _fctry.ConvertFromDto(null))
        .WithAnyArguments()
        .Returns(
            new TextUnit(
                new Dictionary<string,string>(){
                    { "Text", "This is my Text" }
                }
            )
        );
    A.CallTo (() => _container.Resolve<IDisplayUnitsRepository>()).Returns(repo);
    var mgr = new DisplayUnitsRepoManager(_container);
    var duDict = mgr.GetByEvent(VALID_GUID);
    Assert.That(duDict, Is.InstanceOf(typeof(Dictionary<int,DisplayUnit>)));
    Assert.That(duDict, Is.Not.Null);
    Assert.That(duDict.Count == 5);
    foreach(var key in duDict.Keys)
    {
        Assert.That(duDict[key].PositionInEvent == key);
    }
}

所以这里的评论很有启发性。基于这些,我意识到了我需要寻找的方向。这里的罪魁祸首是:

 A.CallTo(() => _fctry.ConvertFromDto(null))
    .WithAnyArguments()
    .Returns(
        new TextUnit(
            new Dictionary<string,string>(){
                { "Text", "This is my Text" }
            }
        )
    );

本质上,这与 FakeItEasy 及其伪造 return 值的方式有关。即使我在 return 值中新建了一个 TextUnit,FakeItEasy 还是获取了那个新对象,并且 return 在 _fctry.ConvertFromDto() 被调用时引用了它。因此,我的假货给了我奇怪的行为,否则就不会发生(我永远不会通过引用将相同的项目多次添加到字典中)。

无论如何,我可以通过更改我的 return 规范来解决这个问题:

A.CallTo (() => _fctry.ConvertFromDto (null))
    .WithAnyArguments()
    .ReturnsLazily((DisplayUnitDTO d) => new TextUnit(d.Attributes));

在我对此进行测试后,每次调用该函数时都会创建一个新的文本单元。 (顺便说一句......我知道我实际上并没有在 lambda 中使用 d,但我需要使用与调用相同的签名来伪造 return 值。)

感谢评论者和他们的指点。我已经重命名了这个问题,以便更好地与实际发生的事情联系起来。