Eamon Nerbonne
Eamon Nerbonne

Reputation: 48126

Determining whether a struct is of default value without "Equals"; aka ReferenceEquals for structs

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

Answers (7)

FunctorSalad
FunctorSalad

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

Eamon Nerbonne
Eamon Nerbonne

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

user541686
user541686

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.


Note:

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 structs are.


Hackier Solution:

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

DiVan
DiVan

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

Michael Edenfield
Michael Edenfield

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

InBetween
InBetween

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

Grant Thomas
Grant Thomas

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

Related Questions