Reputation: 48126
For some general helper methods I'm writing, I'd like to be able to invoke special processing when that value is the default value of its type. For reference types, that's easy - the default value is null
. I can't use a generic type parameter, although I could work around that.
I can do something like this:
public bool DetectPossiblyUninitializedValue(object val) {
return val== null ||
val.GetType().IsValueType
&& Equals(val, Activator.CreateInstance(val.GetType());
}
That's what I'm using right now, but it depends on the implementation of Equals
. That's fine, but not ideal. In particular, some implementations might override Equals to support more usable semantics in normal scenarios. It's actually not uncommon to treat the default value as special here because it's so unavoidable in .NET due to default initialization.
However, in this case, I just want to know whether the object may have been initialized, and I therefore don't want any custom equality or whatever. Basically, I want to know whether the memory region the struct occupies is filled with zero's as the VM guarrantees after initialization, and no more. In a sense I'm looking for something akin to ReferenceEquals
for structs: a comparison disregarding the underlying object's own implementation.
How can I compare raw struct values without using Equals
? Can I compare raw struct values at all?
Edit: I'm using this to hookup classes+structs representing domain-specific notions connected by essentially arbitrary code representing various business rules to a GUI. Some old code essentially deals with possibly nested dictionaries of string to arbitrary objects, which thus requires a bunch of unchecked casts or dynamic
; creating these is error prone. So it's nice to be able to work with the typed objects relatively directly. On the other hand, it's useful for the GUI and wrapping code to treat possibly uninitialized values differently; and although a case-by-case, type-by-type solution is possible, that's lots of code; a sensible default is useful. Really what I want is a method of automatically generating a type that's identical to another but with all properties/public fields extended to include a value "uninitialized", but that's not a realistic feature to expect - by contrast in a dynamic world this would be trivially achievable, though without typesafety elsewhere...
Answers: Mehrdad posted an answer on how to directly access the bits of structs; I added an implementation using that to detect possibly uninitialized values.
Upvotes: 9
Views: 5598
Reputation: 2622
Eamon Nerbonne's answer can now be implemented using System.Runtime.CompilerServices.Unsafe without having to use undocumented/unsupported features and raw pointers:
// 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;
}
}
Upvotes: 1
Reputation: 48126
Original Poster here: I've settled on ...not... using the solution below, extended from Mehrdad's notes. It works, but I don't think the overall trickiness is worth it to catch a few more uninitialized values in the default implementation.
But if others ever care here it is:
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
}
Upvotes: 1
Reputation: 210725
If you're worried about the overhead of boxing (and you've measured that this is a bottleneck), you can solve it differently:
Create two temporary boxed instances of your struct as an object
, which can be reused for all structs. Using Reflection.Emit
, create a method that uses the Unbox
opcode to copy a struct to a the boxed version. (This lets you avoid an allocation.) Do the same thing with the other boxed struct, then call Equals
on the objects.
I don't know if the overhead of a delegate call is actually faster, but you could try anyway and see. If you find out it's not, then you could always do more than one comparison at once -- pass in an array or something. It gets complicated, but if you know this is the bottleneck then it might be worth it, depending on how big your struct
s are.
I'm not supporting this solution, merely suggesting that it exists. If you don't know what this is doing, don't use it.
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.
}
To find the size of a struct:
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)); }
}
}
Yes, it'd kind of undocumented. But if you're worried about that, you could just emit this method instead (because the MkRefAny
opcode is indeed documented), so that's not an issue. However, this example can break on other platforms, so be careful...
Upvotes: 4
Reputation: 381
Can I compare raw struct values at all? - No. CLR itself uses reflection in order to compare two structures field-by-field. Equals is your only hope. Value types should implement Equals, that is not differ from field-by-field reflection comparison. Otherwise, the Value Type is not a ValueType.
Consider the following
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));
}
What would you expect to receive? We're talking structs here.
Upvotes: 0
Reputation: 28338
General-purpose struct comparison needs to be done using something like Reflection -- basically, you need to compare each field in the structures separately. You could use unsafe/unmanaged code to, for example, copy the structure into a byte[] and scan for non-zero bytes, but relying on underlying VM guarantees like that may be a bad idea. (C#, the language, only guarantees that each field has its "default" value -- the fact that the default value is 0 is a CLR-specific detail that may change.)
There's several solutions for comparing structs, including a fairly compact LINQ solution, in the answers to Compare two structs' values in C#.
You can use the default
keyword to get a default structure to compare with, like:
var blank = default(type)
Building on that LINQ solution, this ought to do what you want:
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();
}
EDIT:
If your structs, in turn, have their own members that are structs, this will unfortunately fall back on .Equals()
to compare those. If that's a problem, using a more long-winded foreach
loop over the fields and handling the struct-type fields separately would also work, on the same principle.
Upvotes: 0
Reputation: 32780
If the Value Types
you are considering are all "under your control" or will be tailor made to work with your code you can always make them implement a readonly bool IsInitialized
field and check it through reflection.
If not, I find it hard to do what you want without using Equals
. You could theoretically iterate through the fields using reflection to check if all fields are set to their default values.
Upvotes: 0
Reputation: 45068
With the limited time I have to understand your requirements, I'm just going to throw something out here for you to ponder. though it does involve operator overloading (which, in turn, is implementation specific):
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);
}
}
Then, to compare:
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");
}
Upvotes: 2