对于我正在编写的一些常规辅助方法,我希望能够在该值是其类型的默认值时调用特殊处理.对于参考类型,这很容易 - 默认值是null.我不能使用泛型类型参数,虽然我可以解决这个问题.
我可以这样做:
public bool DetectPossiblyUninitializedValue(object val) {
return val== null ||
val.GetType().IsValueType
&& Equals(val, Activator.CreateInstance(val.GetType());
}
Run Code Online (Sandbox Code Playgroud)
这就是我现在正在使用的,但这取决于它的实现Equals.那很好,但不理想.特别是,某些实现可能会覆盖Equals以支持正常方案中更多可用的语义.在这里将默认值视为特殊情况实际上并不罕见,因为由于默认初始化,它在.NET中是不可避免的.
但是,在这种情况下,我只想知道对象是否已经初始化,因此我不想要任何自定义相等或其他.基本上,我想知道结构占用的内存区域是否填充为零,因为初始化后VM保证,而不是更多.从某种意义上说,我正在寻找类似于ReferenceEquals结构的东西:无视底层对象自身实现的比较.
如何在不使用的情况下比较原始结构值Equals?我可以比较原始结构值吗?
编辑:我正在使用它来连接表示特定于域的概念的类+结构,这些概念由表示各种业务规则的基本上任意代码连接到GUI.一些旧的代码基本上处理可能嵌套的字符串到任意对象的字典,因此需要一堆未经检查的强制转换或者dynamic; 创建这些是容易出错的.因此能够相对直接地处理类型化对象很好.另一方面,GUI和包装代码以不同方式处理可能未初始化的值是有用的; 虽然逐个案例,逐个类型的解决方案是可能的,这是很多代码; 合理的默认值很有用.我真正想要的是一种自动生成一种类型的方法,该类型与另一种类型相同,但所有属性/公共字段都扩展为包含"未初始化"值,但这不是一个期望的现实特征 - 相比之下,在动态世界中这将是虽然在其他地方没有类型安全,但是可以轻易实现......
答案: Mehrdad发布了一个关于如何直接访问结构的答案; 我添加了一个实现,用于检测可能未初始化的值.
如果您担心装箱的开销(并且您已经测量到这是一个瓶颈),您可以用不同的方式解决它:
创建结构体的两个临时装箱实例作为object,它可以重用于所有结构体。使用Reflection.Emit,创建一个方法,该方法使用Unbox操作码将结构复制到盒装版本。(这可以让您避免分配。)对另一个装箱结构执行相同的操作,然后调用Equals对象。
我不知道委托调用的开销是否实际上更快,但您无论如何都可以尝试看看。如果你发现不是,那么你总是可以一次进行多次比较——传入一个数组或其他东西。它变得很复杂,但如果您知道这是瓶颈,那么它可能是值得的,具体取决于您的大小struct。
我不支持这个解决方案,只是建议它存在。如果您不知道这是做什么的,请不要使用它。
bool UnsafeHackyEquals<T>(ref T a, ref T b) where T : struct
{
TypedReference pA = __makeref(a), pB = __makeref(b);
var size = SizeOf<T>();
IntPtr* ppA = (IntPtr*)&pA, ppB = (IntPtr*)&pB;
//Now ppA[0] is a pointer to a, and ppB[0] is a pointer to b.
//You have the size of both, so you can do a bitwise comparison.
}
Run Code Online (Sandbox Code Playgroud)
要查找结构体的大小:
static class ArrayOfTwoElements<T> { static readonly T[] Value = new T[2]; }
static uint SizeOf<T>()
{
unsafe
{
TypedReference
elem1 = __makeref(ArrayOfTwoElements<T>.Value[0] ),
elem2 = __makeref(ArrayOfTwoElements<T>.Value[1] );
unsafe
{ return (uint)((byte*)*(IntPtr*)(&elem2) - (byte*)*(IntPtr*)(&elem1)); }
}
}
Run Code Online (Sandbox Code Playgroud)
是的,这有点无证。但如果您担心这一点,您可以直接发出此方法(因为MkRefAny操作码确实已记录),所以这不是问题。然而,这个例子可能会在其他平台上崩溃,所以要小心......