如何确定两个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.
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)
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项目,但它也只是对一种方法感觉有点过头了.
此外,如果这对您很重要,您可以避免依赖外部库.
请注意,由于可能存在竞争条件,我不会完全信任__makeref和Unsafe.AsPointer解决方案:如果您不幸将这些条件放在一起:
那么,在比较之前GC不会更新已经评估过的指针,因此您将得到不正确的结果.
它可能会发生吗?并不是的.但它可以.
该Unsafe.AreSame方法始终在byref空间中运行,因此GC可以随时跟踪和更新引用.