Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

reflection

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.

like image 935
Eamon Nerbonne Avatar asked Jun 21 '11 15:06

Eamon Nerbonne


People also ask

What is the default value of a struct?

For variables of class types and other reference types, this default value is null . However, since structs are value types that cannot be null , the default value of a struct is the value produced by setting all value type fields to their default value and all reference type fields to null .

Can a struct have default values?

Default values can be assigned to a struct by using a constructor function. Rather than creating a structure directly, we can use a constructor to assign custom default values to all or some of its members. Another way of assigning default values to structs is by using tags.

Is struct pass by value or reference?

A struct is a value type, so it's always passed as a value. A value can either be a reference type (object) or a value type (struct).

Can structs contain reference types?

Yes they can. It depends. Many hold the stance that a struct should be immutable, and in this case, holding a reference to an object could mean it isn't. But it depends on the situation.


1 Answers

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

like image 186
user541686 Avatar answered Sep 17 '22 13:09

user541686