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

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


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");
}

Tags:

C#

Reflection