对于我正在编写的一些常规辅助方法,我希望能够在该值是其类型的默认值时调用特殊处理。对于参考类型,这很容易 - 默认值为null
。我不能使用泛型类型参数,尽管我可以解决这个问题。
我可以这样做:
public bool DetectPossiblyUninitializedValue(object val) {
return val== null ||
val.GetType().IsValueType
&& Equals(val, Activator.CreateInstance(val.GetType());
}
这就是我现在正在使用的内容,但这取决于Equals
的实现。那很好,但不理想。特别是,某些实现可能会覆盖Equals以支持正常方案中更多可用的语义。在这里将默认值视为特殊情况实际上并不罕见,因为由于默认初始化,它在.NET中是不可避免的。
但是,在这种情况下,我只想知道对象是否已经初始化,因此我不想要任何自定义等式或其他。基本上,我想知道结构占用的内存区域是否填充为零,因为初始化后VM保证,而不是更多。从某种意义上说,我正在寻找类似于结构的ReferenceEquals
的东西:一种忽略底层对象自身实现的比较。
如何在不使用Equals
的情况下比较原始结构值?我可以比较原始结构值吗?
编辑: 我正在使用它来连接表示特定于域的概念的类+结构,这些概念由表示各种业务规则的基本上任意代码连接到GUI。一些旧代码基本上处理可能嵌套的字符串到任意对象的字典,因此需要一堆未经检查的强制转换或dynamic
;创建这些是容易出错的。因此能够相对直接地处理类型化对象很好。另一方面,GUI和包装代码以不同方式处理可能未初始化的值是有用的;虽然可以逐个案例地使用逐个类型的解决方案,但是很多代码;合理的默认值很有用。我真正想要的是一种自动生成一种类型的方法,该类型与另一种类型相同,但所有属性/公共字段都扩展为包含“未初始化”值,但这不是一个期望的现实特征 - 相比之下,在动态世界中这将是虽然在其他地方没有类型安全,但是很容易实现......
答案: Mehrdad在how to directly access the bits of structs上发布了答案;我添加了an implementation using that to detect possibly uninitialized values。
答案 0 :(得分:4)
如果您担心拳击的开销(并且测量这是一个瓶颈),您可以采用不同的方式解决:
创建结构的两个临时盒装实例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.
}
要查找结构的大小:
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)); }
}
}
是的,它有点无证。但是如果你担心这一点,你可以只发出这个方法(因为确实记录了MkRefAny
操作码),所以这不是问题。但是,这个例子可以在其他平台上破解,所以要小心......
答案 1 :(得分:2)
由于时间有限,我必须了解你的要求,我只想在这里抛出一些东西供你思考。虽然它确实涉及运算符重载(反过来, 特定于实现):
public struct Foo
{
public int Bar;
public static bool operator ==(Foo a, Foo b)
{
return a.Bar == b.Bar;
}
public static bool operator !=(Foo a, Foo b)
{
return !(a.Bar == b.Bar);
}
public override bool Equals(object obj)
{
return base.Equals(obj);
}
}
然后,比较:
Foo foo1 = new Foo();
Foo foo2 = new Foo { Bar = 1 };
if (foo1 == default(Foo))
{
Console.WriteLine("foo1 is equal to default");
}
if (foo2 != default(Foo))
{
Console.WriteLine("foo2 is not equal to default");
}
答案 2 :(得分:1)
原创海报:我已经决定...... 不 ......使用下面的解决方案,从Mehrdad的笔记中扩展而来。它可以工作,但我认为在默认实现中捕获一些未初始化的值是不值得的。
但如果其他人在这里关心它是:
public static bool PossiblyUninitialized(object a) {
if(a == null) return true;
Type t = a.GetType();
return t.IsValueType &&
helpers.GetOrAdd(t, _=>{
var method = typeof(StructHelpers<>).MakeGenericType(t)
.GetMethod("PossiblyUninitialized");
var objParam = Expression.Parameter(typeof(object),"obj");
return Expression.Lambda<Func<object,bool>>(
Expression.Call(method,Expression.Convert(objParam,t)),
objParam
).Compile();
})(a);
}
static ConcurrentDictionary<Type, Func<object,bool>> helpers =
new ConcurrentDictionary<Type, Func<object,bool>>();
unsafe static class StructHelpers<T> where T : struct {
public static readonly uint ByteCount = SizeOf();
static uint SizeOf()
{
T[] arr = new T[2];
var handle = GCHandle.Alloc(arr);
TypedReference
elem0 = __makeref(arr[0]),
elem1 = __makeref(arr[1]);
return (uint)((byte*)*(IntPtr*)(&elem1) - (byte*)*(IntPtr*)(&elem0));
handle.Free();
}
public static bool PossiblyUninitialized(T a)
{
TypedReference pA = __makeref(a);
var size = ByteCount;
IntPtr* ppA = (IntPtr*)(&pA);
int offset = 0;
while(size - offset>=8) {
if(*(long*)(*ppA+offset) != 0)
return false;
offset+=8;
}
while(size - offset>0) {
if(*(byte*)(*ppA+offset) != 0)
return false;
offset++;
}
return true;
}
}
void Main()//LINQpad
{
StructHelpers<decimal>.ByteCount.Dump();
PossiblyUninitialized(0m).Dump();//true
PossiblyUninitialized(0.0m).Dump();//false
PossiblyUninitialized(0.0).Dump();//true
PossiblyUninitialized(-0.0).Dump();//false
PossiblyUninitialized("").Dump();//false
}
答案 3 :(得分:0)
通用结构比较需要使用像Reflection这样的东西来完成 - 基本上,你需要分别比较结构中的每个字段。例如,您可以使用不安全/非托管代码将结构复制到byte []并扫描非零字节,但依赖于底层VM保证可能是个坏主意。 (C#,该语言,仅保证每个字段都具有“默认”值 - 默认值为0的事实是可能更改的特定于CLR的详细信息。)
在Compare two structs' values in C#的答案中,有几种比较结构的解决方案,包括相当紧凑的LINQ解决方案。
您可以使用default
关键字获取要与之比较的默认结构,例如:
var blank = default(type)
在LINQ解决方案的基础上,这应该做你想做的事情:
static bool IsDefault<T> ( T b ) where T : struct
{
T a = default(T);
var differences = from fielda in a.GetType().GetFields()
join fieldb in b.GetType().GetFields() on fielda.Name equals fieldb.Name
where !fielda.GetValue(a).Equals(fieldb.GetValue(b))
select fielda.Name;
return !differences.Any();
}
编辑:
如果您的结构反过来拥有自己的结构成员,那么不幸的是,.Equals()
会对这些结构进行比较。如果这是一个问题,在字段上使用更长的foreach
循环并单独处理struct-type字段也可以按照相同的原则工作。
答案 4 :(得分:0)
如果您考虑的Value Types
全部“在您的控制之下”,或者将根据您的代码量身定制,您可以随时让它们实现readonly bool IsInitialized
字段并通过反思进行检查。< / p>
如果没有,我发现很难在不使用Equals
的情况下做你想做的事情。理论上,您可以使用反射迭代字段,以检查是否所有字段都设置为默认值。
答案 5 :(得分:0)
我可以比较原始结构值吗? - 否。 CLR本身使用反射来逐个比较两个结构。 等于是你唯一的希望。值类型应该实现Equals,这与逐场反射比较没有区别。否则,Value Type不是ValueType。
考虑以下
struct Bla
{
int Data;
}
...
{
Bla a = new Bla();
Bla b = new Bla();
a.Data = 10;
a.Data = 0;
Console.Writeline(IsDefault(a));
Console.Writeline(IsDefault(b));
}
您期望收到什么?我们在这里谈论结构。
答案 6 :(得分:0)
Eamon Nerbonne的答案现在可以使用System.Runtime.CompilerServices.Unsafe来实现,而不必使用未记录/不受支持的功能和原始指针:
// Essentially unchanged from Eamon Nerbonne's version
public static bool IsDefaultValue([CanBeNull] object a)
{
if (a == null) return true;
Type type = a.GetType();
return type.IsValueType &&
helpers.GetOrAdd(
type,
t =>
{
var method = typeof(StructHelpers<>).MakeGenericType(t)
.GetMethod(nameof(StructHelpers<int>.IsDefaultValue));
var objParam = Expression.Parameter(typeof(object), "obj");
return Expression.Lambda<Func<object, bool>>(
Expression.Call(method, Expression.Convert(objParam, t)),
objParam)
.Compile();
})(a);
}
static readonly ConcurrentDictionary<Type, Func<object,bool>> helpers =
new ConcurrentDictionary<Type, Func<object,bool>>();
static class StructHelpers<T> where T : struct
{
// ReSharper disable StaticMemberInGenericType
static readonly int ByteCount = Unsafe.SizeOf<T>();
static readonly int LongCount = ByteCount / 8;
static readonly int ByteRemainder = ByteCount % 8;
// ReSharper restore StaticMemberInGenericType
public static bool IsDefaultValue(T a)
{
if (LongCount > 0)
{
ref long p = ref Unsafe.As<T, long>(ref a);
// Inclusive end - don't know if it would be safe to have a ref pointing
// beyond the value as long as we don't read it
ref long end = ref Unsafe.Add(ref p, LongCount - 1);
do
{
if (p != 0) return false;
p = ref Unsafe.Add(ref p, 1);
} while (!Unsafe.IsAddressGreaterThan(ref p, ref end));
}
if (ByteRemainder > 0)
{
ref byte p = ref Unsafe.Add(
ref Unsafe.As<T, byte>(ref a),
ByteCount - ByteRemainder);
ref byte end = ref Unsafe.Add(ref p, ByteRemainder - 1);
do
{
if (p != 0) return false;
p = ref Unsafe.Add(ref p, 1);
} while (!Unsafe.IsAddressGreaterThan(ref p, ref end));
}
return true;
}
}