Skip to content

Instantly share code, notes, and snippets.

@VladimirReshetnikov
Created January 15, 2014 20:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save VladimirReshetnikov/8444468 to your computer and use it in GitHub Desktop.
Save VladimirReshetnikov/8444468 to your computer and use it in GitHub Desktop.
using System;
unsafe struct S
{
public void* ptr;
public object obj;
static void Main()
{
S s = default(S);
Console.WriteLine(Equals(s, s));
}
}
@controlflow
Copy link

using System;

unsafe struct S
{
  public object obj;
  public void* ptr;

  static void Main()
  {
    S s = default(S);

    var ptrField = typeof(S).GetField("ptr");

    Console.WriteLine(ptrField.FieldType.FullName); // System.Reflection.Pointer

    var value = ptrField.GetValue(s);
    var otherValue = ptrField.GetValue(s);
    Console.WriteLine(Equals(value, otherValue)); // false, two different boxed instances

    Console.WriteLine(Equals(s, s));
  }
}

When commenting one of the fields, default Equals implementation uses byte-to-byte comparison.
Otherwise, it walks over all the fields with reflection. Since pointer types are not Object-derived (to be exposed from FieldInfo.GetValue()), reflection API packs them into instances of System.Reflection.Pointer ref type. Unfortunately, this type do not overrides Equals() implementation to provide value equality semantics.

@SergeyTeplyakov
Copy link

Interesting, why struct with only one pointer or with pointer and value type behaves correctly?

using System;

unsafe struct S_Ok1
{
    public void* ptr;
    public void* ptr2;
}

unsafe struct S_Ok2
{
    public int n;
    public int* ptr;
}

unsafe struct S
{
    // Could be any reference type
    public object o;
    public int* ptr;
}
class Program
{
    public static void Main()
    {
        S s = default(S);
        S_Ok1 sOk1 = default(S_Ok1);
        S_Ok2 sOk2 = default(S_Ok2);

        Console.WriteLine(Equals(s, s)); // False
        Console.WriteLine(Equals(sOk1, sOk1)); // True
        Console.WriteLine(Equals(sOk2, sOk2)); // True
    }
}

I assume that you ideas is correct, but in case when struct doesn't contains reference type fast version of Equals method is used:

// Inside ValueType.cs
bool Equals(object obj)
{
    //Compare type 

    object a = this;
    if (CanCompareBits(this))
    {
        return FastEqualsCheck(a, obj);
    } 

    //Compare using reflection
}

It seems that CanCompareBits return true only when struct contains reference type but returns true if struct contains unsafe pointer, and this comment from Rotor is not accurate:

The comment of CanCompareBits says "Return true if the valuetype does not contain pointer and is tightly packed". And FastEqualsCheck uses "memcmp" to speed up the comparison.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment