3

Is it possible to compare two managed references (of type ref T) if they are equal? I don't mean references to objects, but references to variables. Example:

public static bool Compare(ref int a, ref int b)
{
    return ref a == ref b; //something like that, not possible this way in C#
}

int x, y;
Compare(ref x, ref x); //true
Compare(ref x, ref y); //false
Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
IS4
  • 11,945
  • 2
  • 47
  • 86
  • Not understanding the question exactly. Do you mean "Do they point to the same object in memory" or "Do they have the same 'value' (as per some definition of value)" ? – Haedrian Nov 16 '14 at 10:33
  • Yeah, I mean if they point to the same place in memory. – IS4 Nov 16 '14 at 10:34

6 Answers6

3

The definitive reference (no pun intended) is here - Equality Comparisons (C# Programming Guide).

You can compare two objects of type T for reference equality by using Object.ReferenceEquals if (and so far as I know only if) T is a reference type.

As Haedrian points out, this is not possible for value types even when they are passed by reference due to boxing in the call to ReferenceEquals.

int x = 0, y = 0;
IsSameReference(ref x, ref x).Dump(); // Passing the same value type variable twice, by reference. We want a result of 'true'
IsSameReference(ref x, ref y).Dump(); // We expect 'false'

public static bool IsSameReference(ref int a, ref int b)
{
    return Object.ReferenceEquals(a, b);
}

Both calls dump false. (Note I renamed the function Compare as that is usually used for sort-ordering comparisons).

Essentially then, where T can be of any type, the answer is no.

(Fun and games with int only removed from answer as superceded by another answer).

Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
  • Nice approach! I see the only problem in making this generic is finding a value that is different from both `a` and `b`. – IS4 Nov 16 '14 at 12:02
  • @HenkHolterman Of course, there are also problems with it like exception handling and thread-safety, but these problems are not so important. – IS4 Nov 16 '14 at 12:07
  • @StephenKennedy: +1. Perhaps it should be pointed out that your `IsSameReference` method might report wrong results in multi-threaded programs, e.g. when `a` and `b` are really distinct, and another thread happens to update `a` to the value of `b` just before *your method* checks whether they are equal. It's unlikely, but possible. You could minimize the probability of this by building a sequence of (say 100) random integer numbers, then assigning each of these to `a`, and comparing `b` against `a`. Only when the equality holds for every random number in the sequence would you return `true`. – stakx - no longer contributing Nov 16 '14 at 12:09
  • @StephenKennedy: (cont'd) And you'd might have to wrap your method in a `lock`, so that it cannot run twice at the same time; more specifically, from where the modification of `a` and `b` begin, up to where they are restored to their original values. – stakx - no longer contributing Nov 16 '14 at 12:11
  • @stakx Good point. I will edit the answer to say the function is not thread-safe and make it explicit that this function is not intended for production usage (which is what I meant by "just for fun"). Thanks! [re: Second comment, a lock statement is exactly what I was thinking but I'm not sure whether to do that or just make it clear that this is not thread safe and probably not useful except as a conceptual example. Will think about it over a coffee] – Stephen Kennedy Nov 16 '14 at 12:13
  • 1
    @StephenKennedy: Read the following after your coffee break. :-) Putting a lock inside this method might make it thread-safe, but it won't solve the problem of your method possibly giving incorrect results: As long as you don't have exclusive control over `a` and `b`, you'll *never* be able to trust your `a == b` comparison. The `lock` will only make sure that two concurrent executions *of your own method* don't interfere; it cannot prevent other concurrent code from modifying `a` and `b`. Locks can only coordinate access to resources between parties that have decided to use the same lock. – stakx - no longer contributing Nov 16 '14 at 12:18
2

Following a suggestion here, below is a fast and efficient method for testing two managed pointers for (reference) equality.

reminder: managed pointers "track" within the interior of their containing GC object and are automatically updated so that they remain valid if the object is moved.

The function is most useful for .NET value type instances (i.e., struct as opposed to class): it will return true if and only if the two managed references point to the same location, either on the stack or within the (same) object in the GC heap:

public static bool IL<T>.RefEquals(ref T a, ref T b)      /* code provided below */

Prior to C#7, the only way to observe a managed pointer in C# was from within a function which has ref or out arguments, but now in 2017 the new version of the language allows you to explicitly declare managed pointers--following certain conditions--as locals or function return values.

As for reference types, this form of reference equality will apply to the reference handle itself, making it a stricter constraint than Object.ReferenceEquals. In other words, not only must the comparands reference the same GC object, but they must do so through the exact same referencing handle.

The code uses a DynamicMethod of only four IL instructions, but has to do the one-time startup work of buiding the method at runtime. First, the complete class. This is the only part you will need. The test and demo code is below.

public static class IL<T>
{
    public delegate bool delRefEq(ref T a, ref T b);

    public static readonly delRefEq RefEquals;   <--- here you go

    static IL()
    {
        var dm = new DynamicMethod(
            "__IL_RefEquals<" + typeof(T).FullName + ">",
            typeof(bool),
            new[] { typeof(T).MakeByRefType(), typeof(T).MakeByRefType() },
            typeof(Object),
            true);

        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ceq);
        il.Emit(OpCodes.Ret);

        RefEquals = (delRefEq)dm.CreateDelegate(typeof(delRefEq));
    }
};

That's all you need. Below is some simple demonstration code for usage. With the advent of ref locals now in C# 7, I expect that the above function will be getting a lot of use.

static unsafe class ref_eq_test
{
    public static bool RefEquals<T>(ref T a, ref T b) => IL<T>.RefEquals(ref a, ref b);

    public static void Test1(Guid g) => Test2(g, ref g, &g);

    static void Test2(Guid g, ref Guid r0, Guid* p0)
    {
        Guid g2 = g;
        ref Guid r2 = ref g;                        // <-- C# 7 only
        ref Guid r3 = ref r0;                       // <-- C# 7 only

        check(RefEquals(ref g, ref g), true);       // identity of values
        check(RefEquals(ref g, ref g2), false);     // not same reference
        check(RefEquals(ref g, ref r0), false);     // as set by caller
        check(RefEquals(ref g, ref *p0), false);    // 'g' is not 'ref'
        check(RefEquals(ref r0, ref r0), true);     // identity of value refs
        check(RefEquals(ref r0, ref *p0), true);    // mixing ref with pointer
        check(RefEquals(ref *p0, ref *p0), true);   // identity of pointer deref
        check(RefEquals(ref r2, ref g), true);      // more cases...
        check(RefEquals(ref r2, ref g2), false);    // 'g2' is not a ref
        check(RefEquals(ref r3, ref *p0), true);    // transitive ref chain
    }

    static void check(bool _exp, bool _act) { if (_exp != _act) throw new Exception(); }
};
Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
  • Nice job, that's actually another solution I came up with. I have even seen a similar one in the official GitHub sources. – IS4 Apr 23 '17 at 15:16
  • @IllidanS4 It's also worth noting that `Object.Equals(Object,Object)` works correctly for comparing managed references. The value types are boxed (if neither is null), so performance could be a concern. Assuming one hasn't overridden the struct's `ValueType.Equals(Object)`, the call ends up at `CompilerServices.RuntimeHelpers.Equals(Object, Object)`, which indeed correctly recognizes that two (i.e., different) boxes of the same managed address are to be considered equivalent. Note that `Object.ReferenceEquals` does **not** work for this, since it insists on the boxes themselves being the same. – Glenn Slayden Nov 25 '17 at 11:56
2

You can use the official Microsoft NuGet package System.Runtime.CompilerServices.Unsafe to accomplish this without having to mark your method as unsafe.

using System.Runtime.CompilerServices;

static void Main()
{
    int value = 0;
    ref int ref1 = ref value;
    ref int ref2 = ref value;
    Debug.Assert(Unsafe.AreSame(ref ref1, ref ref2));
}

Do note that I'd argue this method is not really Unsafe and perhaps should be moved to another class/package along with Unsafe.SizeOf.

josetr
  • 219
  • 1
  • 5
  • 3
1

Pointer comparing works, but it is not generic. TypedReference fortunately works with generics, but there is no way of comparing it without a bit of memory reading. Though this currently works, it is not guaranteed this technique will work in the future.

public static unsafe bool Equals(this TypedReference tr, TypedReference other)
{
    IntPtr* a = ((IntPtr*)&tr);
    IntPtr* b = ((IntPtr*)&other);
    return a[0] == b[0] && a[1] == b[1];
}

public static bool Equals<T>(ref T a, ref T b)
{
    return __makeref(a).Equals(__makeref(b));
}
Community
  • 1
  • 1
IS4
  • 11,945
  • 2
  • 47
  • 86
1

Inspired by, and perhaps slightly improving on this answer by IllidanS4 (and superseding another answer of my own), you can just use the following. I tested it as working with both x86 and amd64 in .NET 4.7.

public static unsafe bool RefEquals<T1, T2>(ref T1 a, ref T2 b)
{
    TypedReference pa = __makeref(a), pb = __makeref(b);
    return *(IntPtr*)&pa == *(IntPtr*)&pb;
}

That's it for the actual code contribution of this article.

You can stop reading here if you're not interested in a more general discussion on the subject of contrasting class versus struct use of ref ("ByRef") referencing.

 


Note: Nullable<T> issues are unrelated to these observations and are not discussed here

Besides condensing two functions into one, the main difference is that this version only considers the first field of the TypedReference value type when checking for equivalence, because that's the extent of the managed pointer it wraps. The second IntPtr-sized field of the TypedReference should arguably be ignored because it just records the Type of the respective target, which "conventionally" perhaps should never affect the outcome of a reference equality determination.

The use of scare-quotes there is meant to highlight the possibility of a choice between design alternatives: namely, whether to heed or ignore claimed Type information in cases where reference equality would otherwise be affirmed. General precedent is for ignoring Type, such as in the case of comparing variegated null references, where the widely expected behavior is to conflate all typed nulls as co-equal.

if (default(Exception) == default(RankException))
{
    /* always true */
}

But is this "precedent" even relevant? Does typed null really have any Type--or for that matter, runtime existence--to then be "ignored" at all? Indeed, extending the earlier reasoning to realistic runtime ByRef comparisons might not be so straightforward. First let's look at the concept of nullability focusing exclusively on what it means in the context of managed "ref" referencing.

An initial observation could be that one's intuitions about nullability--as concerning reference- versus value-type instances--might be reversed when dealing with managed references to each (respectively): for value types, where null values are inherently nonsensical, null reference values becomes extremely relevant and useful, whereas for reference types, for which the notion of null is normally pervasive, it's nearly impossible and almost certainly wrong to ever deal with a null-valued ByRef reference. Here's why: because a ref variable inherently admits the possibility of both reading and writing its target, its type is constrained to be the intersection of the polymorphic co-variance and contra-variance for which it would otherwise be eligible. Obviously, the result of this intersective collapse is a single Type, now necessarily invariant.

Now for value types, the domain of read/write access that ref gives you is your program's data. Since .NET doesn't care what you do in that domain, you can bend the rules and coerce these managed pointers to a certain degree. You can generally muck about to set them to null (despite C#'s protestations) interconvert them with IntPtr, retarget them at well, and the like. For an example, I'll return to IllidanS4's code and the issue of whether conflicting Type information should be disregarded in cases where reference equality is confirmed. As I noted, the issue is moot for his original code since it can't be called with non-identical types. But for the purpose of discussion, here's a version that relaxes the generic type constraint so that the function can actually be entered with disjoint types, but still compares the two TypedReference images in full, presumably then to fail all the newly admitted cases:

public static unsafe bool RefEqualsFull<T1, T2>(ref T1 a, ref T2 b)
{
    TypedReference ra = __makeref(a), rb = __makeref(b);
    IntPtr* pa = (IntPtr*)&ra, pb = (IntPtr*)&rb;
    return pa[0] == pb[0] && pa[1] == pb[1];
}

The difference between this code and my own proposal at the top can be seen with the following:

int i = 1234;
uint* pui = (uint*)&i;

bool b1 = RefEquals(ref i, ref *pui); // TRUE because of RefEq (despite type difference)

bool b2 = RefEqualsFull(ref i, ref *pui); // FALSE despite RefEq (because types differ)

As you can see, with a value type, we CLR will let us abuse the type system since we are only potentially harming ourselves. Unfortunately, C# does not allow some of the most useful ref manipulations, and this is still true even with the outstanding new ref local and ref return features in C# 7. For example, C# (and the vs2017 debugger) still strongly resist null ByRef values (even though they're perfectly fine with the CLR and exposed by other languages such as C++/CLI).

This is a tragedy for the most overwhelmingly obviously scenario for the use of null-valued ref passing, which is to be able to signal special cases when calling APIs that pass pointers. Pervasively with P/Invoke and interop marshaling of legacy data structures, you encounter native APIs that define opt-out behavior where you elect not to receive an "out" parameters by passing in a null pointer for (i.e.):

[DllImport("SomeNativeApi.dll")]
extern unsafe void LegacyAPI([Out, Optional] RECT **ppRect);

If C# (and to be fair I believe the default marshalling is to blame here also) would allow null-valued ref to flow like any other value, you wouldn't have to switch from efficient, lightweight managed pointers to so-called [formatted classes] or somewhat obtrusive Nullable<T> just for the sake of supporting some legacy API's whim.

Switching now to the case of using ref references to reference type (references). There won't be much to say here, because unlike before, the target domain of your read/write ref access is no longer your data, but rather squarely the realm of managed object references, about which .NET cares very deeply.

Remember, when you do a ByRef comparison where the underlying types are reference ("class") types, your comparison has little to do with the values of the references (i.e. the referred-to object instances) any more--that's what's trivially accomplished with Object.ReferenceEquals--you're instead asking whether the two reference handles themselves occupy the same memory address (or not). This is rarely any of your business, so the clamps come down and it's nearly impossible to do the sort of rude manipulations shown above.

As a consolation, it is also quite unusual to need ref access to reference types. So far, I've only come across one practical scenario: discovering at runtime which field of a class instance a certain managed reference "q" might be referring to, if any. In other words:

Given a reference to some ref Object q = ...
...and an instance c = (MyClass)... of class class MyClass { Object o1; Object o2 },
...determine which field in c, if any, can be accessed by reference via q

(...and do this without reading or writing to the field itself).

This is only possible by enumerating the fields of the instance and using the RefEquals function shown above to compare the location indicated by "q" with the location of each field in turn. RefEquals means it will only be possible for the value of "q" to match itself, regardless of its referent (the "value" of the field, whether struct or object-reference) or the referent of the other fields, any of which could be null, uninitialized, or mutually-shared object instances that would foil a "normal" search.

And finally, to satisfy the curious regarding whether the correct answer to the earlier example is true or false, this seems destined to remain an unresolved matter of preference.

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
  • Thanks for an additional input on this matter. I have just two things to point out: ECMA-335 on CLI states that "Managed pointers cannot be null, and they shall be reported to the garbage collector even if they do not point to managed memory.". Null references may not be an actual issue in .NET's CLR (it wasn't when I tried them, and dereferencing threw a *NullReferenceException*). Restricting references in this way makes them substantially different from normal pointers, and allows for other CLI-compatible languages to handle them correctly. I don't think this specification was changed. – IS4 Jul 16 '17 at 10:38
  • As for the other point, I have decided to include type comparison in my answer for two reasons: the *Equals* method compares two *typed* references, hence I thought it appropriate to compare also their types. The second reason are occasions when you obtain a reference to a struct variable and also a reference to its first field. Then, you have two references with effectively equal addresses, but each pointing to undoubtedly different object. The references are equivalent in their address, but not generally interchangeable. Also the generic method effectively restricts them to a single type. – IS4 Jul 16 '17 at 10:44
0

This answer will work for the int type, and does not need to peek at the internal structure of a TypedReference object.

    static bool ReferenceEquals(ref int a, ref int b)
    {
        unsafe
        {
            fixed (int* pA = &a)
            {
                fixed (int* pB = &b)
                {
                    return pA == pB;
                }
            }
        }
    }
Dwedit
  • 618
  • 5
  • 11