53 votes

Comment déterminer si deux variables "ref" se réfèrent à la même variable, même si elles sont nulles?

Comment puis-je déterminer si deux variables ref font référence à la même variable - même si les deux variables contiennent null ?

Exemple:

 public static void Main( string[] args )
{
    object a = null;
    object b = null;

    Console.WriteLine( AreSame( ref a, ref b ) ); // Should print False
    Console.WriteLine( AreSame( ref a, ref a ) ); // Should print True
}

static bool AreSame<T1, T2>( ref T1 a, ref T2 b )
{
    // ?????
}
 

Les choses que j'ai essayées ne fonctionnent pas:

  • return object.ReferenceEquals( a, b ); (Renvoie vrai dans les deux cas de test)
  • unsafe { return &a == &b; } (Impossible de prendre l'adresse d'un objet géré)

43voto

Heinzi Points 66519

Il y a un moyen sans modifier les valeurs, en utilisant le code unsafe et les sans-papiers, __makeref méthode:

public static void Main(string[] args)
{
    object a = null;
    object b = null;

    Console.WriteLine(AreSame(ref a, ref b));  // prints False
    Console.WriteLine(AreSame(ref a, ref a));  // prints True
}

static bool AreSame<T1, T2>(ref T1 a, ref T2 b)
{
    TypedReference trA = __makeref(a);
    TypedReference trB = __makeref(b);

    unsafe
    {
        return *(IntPtr*)(&trA) == *(IntPtr*)(&trB);
    }
}

Remarque: L'expression *(IntPtr*)(&trA) repose sur le fait que le premier champ de TypedReference est un IntPtr pointant vers la variable que l'on souhaite comparer. Malheureusement (ou heureusement?), il n'est pas géré de façon à accéder à ce champ, pas même avec la réflexion, depuis TypedReference ne peut pas être mis en boîte et, par conséquent, ne peut pas être utilisé avec FieldInfo.GetValue.

29voto

Paolo Tedesco Points 22442

Peut-être que cela pourrait être fait en changeant la référence à une variable temporaire et en vérifiant si l'autre change également.
J'ai fait un test rapide, et cela semble fonctionner:

 static bool AreSame(ref object a, ref object b) {
    var old_a = a;
    a = new object();
    bool result = object.ReferenceEquals(a, b);
    a = old_a;
    return result;
}

static void Main(string[] args) {
    object a = null;
    object b = null;

    var areSame1 = AreSame(ref a, ref b); // returns false
    var areSame2 = AreSame(ref a, ref a); // returns true
}
 

29voto

Lucas Trzesniewski Points 28646

Vous pouvez en fait juste utiliser l' Unsafe.AreSame méthode à partir du Système.Moment de l'exécution.CompilerServices.Dangereuses en colis.

Cela permettra de comparer les références directement et est le plus propre solution. La méthode est écrite dans IL et compare simplement les références, parce que, eh bien... vous pouvez le faire en IL :)

Si vous voulez comparer les deux références de différents types, vous pouvez lancer l'un d'entre eux à l'aide de cette surcharge de Unsafe.As:

static bool AreSame<T1, T2>(ref T1 a, ref T2 b) 
    => Unsafe.AreSame(ref Unsafe.As<T1, T2>(ref a), ref b);

Voici une autre suggestion si la coulée de référence se sent maladroit: utiliser mon InlineIL.Fody de la bibliothèque qui vous permet d'injecter arbitraire de code IL directement dans votre code C#:

static bool AreSame<T1, T2>(ref T1 a, ref T2 b)
{
    IL.Emit.Ldarg(nameof(a));
    IL.Emit.Ldarg(nameof(b));
    IL.Emit.Ceq();
    return IL.Return<bool>();
}

Je dis cela, car il est plus facile que émettant code au moment de l'exécution avec la Réflexion.Émettre, parce que vous ne pouvez pas créer un générique DynamicMethod et vous avez besoin de générer une dynamique de type. Vous pouvez également écrire un IL du projet, mais il se sent aussi exagéré juste pour une méthode.

Aussi, vous éviter de prendre une dépendance sur une bibliothèque externe, si c'est important pour vous.


Note que je ne serais pas totalement confiance à l' __makeref et Unsafe.AsPointer solutions en raison de la possibilité d'une condition de concurrence: si vous êtes assez malheureux pour obtenir ces conditions:

  • les deux références sont égaux
  • la GC est déclenchée par un autre thread après le premier côté de la comparaison est évalué mais avant que l'autre est
  • vos points de référence quelque part pour le tas managé
  • l'objet référencé est déplacé par le GC pour des tas de compactage des fins de

Ainsi, alors que le pointeur qui a déjà été évalués ne seront pas mis à jour par le conseil d'administration avant la comparaison, de sorte que vous obtiendrez un résultat incorrect.

Est-il susceptible de se produire? Pas vraiment. Mais il pourrait.

L' Unsafe.AreSame méthode fonctionne toujours en byref de l'espace, de sorte que le GC peut suivre et mettre à jour les références à tout moment.

0voto

SWB Points 360

Voici une autre solution qui n'utilise pas le mot clé __makeref . Cela utilise le package NuGet System.Runtime.CompilerServices.Unsafe :

 using System.Runtime.CompilerServices;

public static void Main( string[] args )
{
    object a = null;
    object b = null;

    Console.WriteLine( AreSame( ref a, ref b ) ); // Prints False
    Console.WriteLine( AreSame( ref a, ref a ) ); // Prints True
}

unsafe static bool AreSame<T1, T2>( ref T1 a, ref T2 b )
{
    var pA = Unsafe.AsPointer( ref a );
    var pB = Unsafe.AsPointer( ref b );

    return pA == pB;
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X