2016-05-15 1 views
1

J'essaie de faire quelque chose qui semble être facile, mais ça ne marche pas. J'ai un dictionnaire d'objets avec une clé int. Au sein des objets, j'ai une propriété, PositionInEvent, que je veux faire correspondre la clé du dictionnaire en son sein. Il semble que cela devrait être une simple opération en boucle, mais cela ne fonctionne pas. Voici ce que j'ai:Pourquoi les éléments du dictionnaire sont-ils ajoutés par référence en C#?

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

Quand je lance ce sur un dictionnaire de 5 objets avec les touches de 0-4 (il ne sera pas toujours séquentielle comme ça, mais je suis tester l'unité), la PositionInEvent propriété sur chaque élément de l'événement a une valeur de 4. Chaque seul. Pourquoi???? Comment puis-je faire ce que j'essaie de faire. Il semble que cela devrait être simple.

Mise à jour:

Il a été demandé que je montre comment DisplayUnit est déclarée, instancié et ajouté au dictionnaire.

est ici la déclaration de classe (je l'ai fait sortir les choses sans rapport avec l'instanciation et la propriété, je travaille avec ici):

/// <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 est la classe que je suis en fait utiliser, ce qui dérive de 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 
} 

Le dictionnaire sur lequel on agit provient d'ici. _duRepo est un dépôt falsifié (voir le code ci-dessous).

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

Tout cela vient de ce test unitaire (que je ne peux pas passer, et je ne sais pas pourquoi):

[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); 
    } 
} 
+4

Comment créez-vous l'objet Unité d'affichage. Il semble que les quatre éléments du dictionnaire référencent la même instance DusplayUnit. – ShuberFu

+0

Pouvez-vous montrer la déclaration de la 'DisplayUnit' aussi? btw: le mot-clé 'ref' semble inutile puisque vous changez seulement le contenu de ce dictionnaire, pas la référence elle-même, mais cela ne devrait pas être l'erreur. –

+1

J'ai créé un simple JS Fiddle pour cela: https://dotnetfiddle.net/T1Z9PW. Je suis d'accord avec Sterling W que votre construction du dictionnaire est suspecte. – syazdani

Répondre

2

Ainsi, les commentaires étaient instructif. Basé sur ceux-là, j'ai réalisé la direction dont j'avais besoin pour regarder. Le coupable ici était la ligne:

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

Essentiellement, cela avait à voir avec FakeItEasy et la façon dont il falsifie des valeurs de retour. Même si j'avais ajouté un TextUnit à la valeur de retour, FakeItEasy a pris ce nouvel objet et lui a renvoyé une référence à chaque fois que _fctry.ConvertFromDto() a été appelée. Ainsi, mon faux me donnait un comportement étrange qui autrement n'aurait pas eu lieu (je n'ajouterais jamais le même article plusieurs fois à un dictionnaire par référence).

Quoi qu'il en soit, j'ai pu corriger cela en changeant ma spécification de retour:

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

Après avoir testé ceci, cela crée une nouvelle unité de texte à chaque fois que la fonction est appelée. (btw ... Je sais que je n'ai pas réellement utilisé d dans le lambda, mais je devais simuler la valeur de retour en utilisant la même signature que l'appel serait fait avec.)

Merci pour les commentateurs et leurs pointeurs. J'ai renommé cette question pour mieux comprendre ce qui se passait réellement.

+0

Le point crucial est "quand les arguments de fonction sont-ils évalués?": Avant qu'une fonction ne soit appelée. L'original 'Returns (new TextUnit (...))' doit créer le 'TextUnit' en premier, donc il ne peut y avoir qu'une seule valeur utilisée. C'est comme si vous aviez écrit 'var theTextUnit = new TextUnit (...); A.CallTo (...) .Retours (theTextUnit); '. Dans ce cas, vous n'auriez pas été surpris si le même 'TextUnit' était utilisé à chaque fois. –