C# 比较两个托管引用
如果两个托管引用(类型为C# 比较两个托管引用,c#,.net,C#,.net,如果两个托管引用(类型为ref T)相等,是否可以对它们进行比较?我不是指对对象的引用,而是对变量的引用。例如: 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)
ref T
)相等,是否可以对它们进行比较?我不是指对对象的引用,而是对变量的引用。例如:
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
这里有明确的参考(没有双关语)
通过使用if(就我所知,仅当)T
是引用类型,您可以比较两个T
类型的对象以获得引用相等性
正如Haedrian指出的,这是由于调用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);
}
两个调用都转储false
。(注意我重命名了函数Compare
,因为它通常用于排序比较)
基本上,如果T
可以是任何类型,答案是否定的
(只有当int被另一个答案取代时,才会从答案中删除int的乐趣和游戏)。有效,但它不是通用的。幸运的是,它可以用于泛型,但是如果没有一点内存读取,就无法进行比较。尽管这项技术目前有效,但不能保证这项技术在未来会有效
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));
}
公共静态不安全布尔等于(此类型引用tr,类型引用其他)
{
IntPtr*a=((IntPtr*)&tr);
IntPtr*b=((IntPtr*)和其他);
返回a[0]==b[0]&&a[1]==b[1];
}
公共静态布尔等于(参考T a,参考T b)
{
返回uu makeref(a)。等于(u makeref(b));
}
根据建议,这是测试两个托管指针(引用)相等性的快速有效版本。该函数对于.NET
值类型实例(即结构
而不是类
)最有用:当且仅当两个托管引用指向GC堆中的同一“位置”,它将返回true(引号提醒您,托管指针的全部意义在于它们“跟踪”GC对象,即使其物理地址发生更改,它们仍然有效):
公共静态bool IL.RefEquals(ref T a,ref T b)/*下面提供的代码*/
在C#7之前,在C#中观察托管指针的唯一方法是从具有ref
或out
参数的函数中观察,但在2017年,新版本的语言允许您显式地将托管指针声明为局部变量或函数返回值(遵循某些条件)
对于引用类型,这种形式的引用相等将应用于引用句柄本身,使其成为比对象更严格的约束。ReferenceEquals
。换句话说,比较对象不仅必须引用相同的GC对象,而且必须通过完全相同的引用句柄来引用
该代码只使用了四条IL指令的DynamicMethod
,但必须在运行时一次性启动构建该方法。首先是完整的类。这是您唯一需要的部分。测试和演示代码如下
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));
}
};
受IllidanS4的启发,或者在IllidanS4的基础上有所改进(并取代了我自己的另一个答案),您可以使用以下内容。我在.NET 4.7中测试了它,将其与
x86
和amd64
一起使用
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;
}
但这一“先例”是否相关?类型化的null
是否真的有任何类型
——或者对于这个问题,运行时的存在——然后被“忽略”实际上,将早期的推理扩展到现实运行时的ByRef比较可能不是那么简单。首先,让我们看看可空性的概念,专门关注它在托管“ref
”引用上下文中的含义
一个初步的观察结果可能是,当处理对每个实例的托管引用时,一个人关于可空性的直觉(关于引用与值类型实例的直觉)可能是相反的:对于值类型,其中null
值本质上是无意义的,null
引用值变得非常相关和有用,而对于引用类型,null
的概念通常很普遍,处理null
-值ByRef
引用。原因如下:因为ref
变量本身就允许读取和写入其目标,所以它的类型被限制为多态性的交集,否则它将符合该交集的条件。显然,这种交集折叠的结果是一个single类型
,现在必须保持不变
现在对于值类型,ref
提供给您的读/写访问域是您的程序的数据。由于.NET不关心您在该域中做什么,您可以在一定程度上扭曲规则并强制这些托管指针。您通常可以将它们设置为null
(尽管C#的声明)使用IntPtr
相互转换它们,在井处重新定位它们,等等。例如,我将返回IllidanS4的代码,以及在确认引用相等的情况下是否应忽略冲突的类型
信息的问题。正如我所指出的,这个问题对于他的原始代码没有意义,因为它不能用非相同类型。但为了讨论的目的,这里有一个版本放宽了泛型类型约束,这样函数实际上可以使用不相交的类型输入,但仍然完整地比较了两个TypedReference
图像,可能会使所有新承认的情况失败:
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];
}
如您所见,对于值类型,我们CLR
会让我们滥用类型系统,因为我们只会潜在地伤害自己。不幸的是,C#不允许一些最有用的ref
操作,即使在C#7中有突出的新功能,这仍然是正确的。例如,C#(以及vs2017
调试器)st
if (default(Exception) == default(RankException))
{
/* always true */
}
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];
}
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)
[DllImport("SomeNativeApi.dll")]
extern unsafe void LegacyAPI([Out, Optional] RECT **ppRect);
static bool ReferenceEquals(ref int a, ref int b)
{
unsafe
{
fixed (int* pA = &a)
{
fixed (int* pB = &b)
{
return pA == pB;
}
}
}
}
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));
}