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

SWB*_*SWB 53 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 )
{
    // ?????
}
Run Code Online (Sandbox Code Playgroud)

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

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

Hei*_*nzi 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • ***无证**方法*听起来很有趣 (13认同)
  • *叹气*,看不到香蕉.不错的解决方案! (8认同)

Pao*_*sco 29

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

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
}
Run Code Online (Sandbox Code Playgroud)

  • "你能把香蕉拿一会儿吗?","是的,当然,但为什么?","嗯,你现在拿着香蕉吗?","是的,嗯,你刚刚告诉我要拿香蕉;再说一次,为什么?","好的,谢谢你,现在把我的香蕉给我!","但为什么,香蕉什么的?" (27认同)
  • @LasseVågsætherKarlsen:你是对的.我更新了我的答案,即使我很抱歉没有一个名为`banana`的变量了. (3认同)
  • 最好使用`a = new object()`,因为这样可以消除'b`已经设置为`banana`的任何可能性. (2认同)

Luc*_*ski 29

实际上,你可以只使用Unsafe.AreSame方法System.Runtime.CompilerServices.Unsafe包.

这将直接比较参考,是最干净的解决方案.这个方法是用IL编写的,只是比较引用,因为,好吧......你可以在IL中做到这一点:)

如果要比较两个不同类型的引用,可以使用以下重载Unsafe.As来强制转换其中一个:

static bool AreSame<T1, T2>(ref T1 a, ref T2 b) 
    => Unsafe.AreSame(ref Unsafe.As<T1, T2>(ref a), ref b);
Run Code Online (Sandbox Code Playgroud)

如果投射引用感觉笨重,这是另一个建议:使用我的InlineIL.Fody库,它允许您将任意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>();
}
Run Code Online (Sandbox Code Playgroud)

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

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


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

  • 两个参考是相等的
  • 所述GC被另一个线程触发的比较的第一侧被评估,但之前的另一个是
  • 您的引用点位于托管堆的某个位置
  • 引用的对象由GC移动以进行堆压缩

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

它可能会发生吗?并不是的.但它可以.

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