如何确定两个“ref”变量是否引用相同的变量,即使是null?

时间:2018-06-13 07:11:33

标签: c#

如何判断两个ref变量是否引用相同的变量 - 即使两个变量都包含null

示例:

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 )
{
    // ?????
}

我尝试过的东西不起作用:

  • return object.ReferenceEquals( a, b );(在两个测试用例中都返回true)
  • unsafe { return &a == &b; }(无法获取托管对象的地址)

4 个答案:

答案 0 :(得分:43)

有一种方法可以使用不安全的代码和未记录的__makeref方法修改值:

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

注意:表达式*(IntPtr*)(&trA)依赖于the first field of TypedReference是指向我们要比较的变量的IntPtr这一事实。不幸的是(或者幸运的是?),没有管理方式来访问该字段 - 甚至没有反射,因为TypedReference无法装箱,因此无法与FieldInfo.GetValue一起使用。

答案 1 :(得分:29)

也许可以通过更改对临时变量的引用并检查另一个变量是否也发生变化来完成 我做了一个快速测试,这似乎有效:

test-1

答案 2 :(得分:29)

您实际上只需使用Unsafe.AreSame method包中的System.Runtime.CompilerServices.Unsafe

这将直接比较引用,并且是最干净的解决方案。这个方法是用IL编写的,只是简单地比较了引用,因为,你好可以在IL中做到这一点:)

如果您想比较不同类型的两个引用,可以使用this overload of Unsafe.As强制转换其中一个:

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

这是另一个建议,如果投射引用感觉笨拙:使用my InlineIL.Fody library,它允许您将任意IL代码直接注入您的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>();
}

我建议这样做,因为它比使用Reflection.Emit在运行时发出代码更容易,因为您无法创建通用DynamicMethod,并且您需要生成动态类型。你也可以编写一个IL项目,但它对于一种方法也感觉有点过分了。

此外,如果这对您很重要,您可以避免依赖外部库。

请注意,由于可能存在竞争条件,我不会完全信任__makerefUnsafe.AsPointer解决方案:如果您不幸遇到这些条件一起:

  • 两个引用相等
  • 在评估比较的第一面之后,另一个线程触发GC,但之前另一个
  • 您的参考点指向托管堆
  • 引用的对象由GC移动以实现堆压缩目的

那么,在比较之前,GC不会更新已经评估过的指针,因此你得到的结果不正确。

是否可能发生?并不是的。但它可以

Unsafe.AreSame方法始终在byref空间中运行,因此GC可以随时跟踪和更新引用。

答案 3 :(得分:0)

这是另一个不使用未记录的__makeref关键字的解决方案。这使用了System.Runtime.CompilerServices.Unsafe NuGet包:

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