Comment attribuer par "référence" un champ de classe dans c#?

j'essaie de comprendre comment attribuer par "référence" à un champ de classe en c#.

j'ai l'exemple suivant à considérer:

 public class X
 {

  public X()
  {

   string example = "X";

   new Y( ref example );

   new Z( ref example );

   System.Diagnostics.Debug.WriteLine( example );

  }

 }

 public class Y
 {

  public Y( ref string example )
  {
   example += " (Updated By Y)";
  }

 }

 public class Z
 {

  private string _Example;

  public Z( ref string example )
  {

   this._Example = example;

   this._Example += " (Updated By Z)";

  }

 }

 var x = new X();

lors de l'exécution du code ci-dessus la sortie est:

X (Mis À Jour Par Y)

et non:

X (Mis À Jour Par Y) (Mis À Jour Par Z)

comme je l'avais espéré.

il semble que l'assignation d'un" paramètre ref " à un champ perde la référence.

Est-il possible de garder la référence lors de l'attribution d'un champ?

Merci.

25
demandé sur Jamie 2010-06-05 17:11:56

3 réponses

Pas de. ref est purement une convention d'appel. Vous ne pouvez pas l'utiliser pour qualifier un champ. Dans Z, _Example obtient la valeur de la référence de chaîne passée. Vous lui assignez alors une nouvelle référence de chaîne en utilisant +=. Vous ne donnez jamais d'exemple, donc l'arbitre n'a pas d'effet.

La seule solution pour ce que vous voulez est d'avoir partagé mutable objet wrapper (un tableau ou d'un hypothétique ' StringWrapper) qui contient la référence (une chaîne). Généralement, si vous avez besoin d' ceci, vous pouvez trouver un objet mutable plus grand pour les classes à partager.

 public class StringWrapper
 {
   public string s;
   public StringWrapper(string s)
   {
     this.s = s;
   }

   public string ToString()
   {
     return s;
   }
 }

 public class X
 {
  public X()
  {
   StringWrapper example = new StringWrapper("X");
   new Z(example)
   System.Diagnostics.Debug.WriteLine( example );
  }
 }

 public class Z
 {
  private StringWrapper _Example;
  public Z( StringWrapper example )
  {
   this._Example = example;
   this._Example.s += " (Updated By Z)";
  }
 }
9
répondu Matthew Flaschen 2010-06-05 13:28:40

comme d'autres l'ont fait remarquer, vous ne pouvez pas avoir un champ de type" ref to variable". Cependant, le simple fait de savoir que vous ne pouvez pas le faire n'est probablement pas satisfaisant; vous voulez probablement aussi savoir d'abord, pourquoi, et ensuite, comment contourner cette restriction.

la raison en est qu'il n'y a que trois possibilités:

1) refus des champs de type ref

2) permettre des champs dangereux de type ref

3) ne utiliser le pool de stockage temporaire pour les variables locales (alias "la pile")

supposons que nous permettions les champs de type ref. Alors vous pourriez faire

public ref int x;
void M()
{
    int y = 123;
    this.x = ref y;
}

et maintenant, y peut être consulté après M complète. Cela signifie que soit nous sommes dans le cas (2) -- accéder à this.x va s'écraser et mourir horriblement parce que le stockage pour y n'existe plus -- ou nous sommes dans le cas (3), et le local y est stocké sur le tas d'ordures collectées, pas le bassin de mémoire temporaire.

nous aimons l'optimisation que les variables locales soient stockées sur la piscine temporaire, même si elles sont passées par ref, et nous détestons l'idée que vous pourriez laisser une bombe à retardement qui pourrait faire planer votre programme et mourir plus tard. Par conséquent, la première option est: aucun champ ref.

noter que pour les variables locales qui sont des variables fermées des fonctions anonymes nous choisissons l'option (3); ces variables locales ne sont pas attribuées de la temporaire de la piscine.

puis nous amène à la deuxième question: comment la contourner? Si la raison pour laquelle vous voulez un champ ref est de faire un getter et setter d'une autre variable, c'est parfaitement légal:

sealed class Ref<T>
{
    private readonly Func<T> getter;
    private readonly Action<T> setter;
    public Ref(Func<T> getter, Action<T> setter)
    {
        this.getter = getter;
        this.setter = setter;
    }
    public T Value { get { return getter(); } set { setter(value); } }
}
...
Ref<int> x;
void M()
{
    int y = 123;
    x = new Ref<int>(()=>y, z=>{y=z;});
    x.Value = 456;
    Console.WriteLine(y); // 456 -- setting x.Value changes y.
}

et voilà. y est stocké sur le tas gc, et x est un objet qui a la capacité d'obtenir et de définir y .

notez que le CLR appuie les locaux de la fre et la FRE méthodes de retour, bien que C# Ne le fait pas. Peut-être qu'une future version hypothétique de C# supportera ces caractéristiques; Je l'ai prototypé et il fonctionne bien. Cependant, ce n'est pas très important sur la liste des priorités, donc je ne me ferais pas de faux espoirs.

mise à jour: la fonctionnalité mentionnée dans le paragraphe ci-dessus a finalement été mise en œuvre pour de vrai dans C# 7. Cependant, vous ne pouvez toujours pas stocker un arbitre dans un champ.

60
répondu Eric Lippert 2018-01-03 01:36:36

vous avez oublié de mettre à jour la référence de la classe Z:

public class Z {
    private string _Example;

    public Z(ref string example) {
        example = this._Example += " (Updated By Z)";
    }
}

Sortie: X (Mis À Jour Par Y) (Mis À Jour Par Z)

Point à garder à l'esprit que l'opérateur += pour une chaîne d'appels de la Chaîne.Méthode Concat (). Qui crée un nouvel objet string, il ne met pas à jour la valeur d'une chaîne. Les objets String sont immuables, la classe string n'a pas de méthodes ou de champs qui vous permettent de changer la valeur. Très différent du comportement par défaut d'un régulier type de référence.

Donc si vous utilisez une méthode de chaîne ou de l'opérateur, vous devez affecter la valeur de retour d'une variable. C'est une syntaxe assez naturelle, les types de valeurs se comportent de la même manière. Votre code serait très similaire si vous utilisiez un int au lieu d'une chaîne.

3
répondu Hans Passant 2010-06-05 13:49:55