Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# - Value Type Equals method - why does the compiler use reflection?

I just came across something pretty weird to me : when you use the Equals() method on a value type (and if this method has not been overriden, of course) you get something very very slow -- fields are compared one to one using reflection ! As in :

public struct MyStruct{
   int i;
}

   (...)

   MyStruct s, t;
   s.i = 0;
   t.i = 1;
   if ( s.Equals( t ))   /*  s.i will be compared to t.i via reflection here. */
      (...)

My question : why does the C# compiler do not generate a simple method to compare value types ? Something like (in MyStruct's definition) :

   public override bool Equals( Object o ){
      if ( this.i == o.i )
         return true;
      else
         return false;
   }

The compiler knows what are the fields of MyStruct at compile time, why does it wait until runtime to enumerate MyStruct fields ?

Very strange to me.

Thanks :)

ADDED : Sorry, I just realize that, of course, Equals is not a language keyword but a runtime method... The compiler is completely unaware of this method. So it make sens here to use reflection.

like image 800
Sylvain Rodrigue Avatar asked Jun 17 '09 20:06

Sylvain Rodrigue


3 Answers

The following is the decompiled ValueType.Equals method from mscorlib:

public override bool Equals(object obj)
{
    if (obj == null)
    {
        return false;
    }
    RuntimeType type = (RuntimeType) base.GetType();
    RuntimeType type2 = (RuntimeType) obj.GetType();
    if (type2 != type)
    {
        return false;
    }
    object a = this;
    if (CanCompareBits(this))
    {
        return FastEqualsCheck(a, obj);
    }
    FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
    for (int i = 0; i < fields.Length; i++)
    {
        object obj3 = ((RtFieldInfo) fields[i]).InternalGetValue(a, false);
        object obj4 = ((RtFieldInfo) fields[i]).InternalGetValue(obj, false);
        if (obj3 == null)
        {
            if (obj4 != null)
            {
                return false;
            }
        }
        else if (!obj3.Equals(obj4))
        {
            return false;
        }
    }
    return true;
}

When possible, a bit-wise comparison will be done (note the CanCompareBits and FastEqualsCheck, both of which are defined as InternalCall. The JIT would presumably inject the appropriate code here. As to why it is so slow, I couldn't tell you.

like image 62
snarf Avatar answered Oct 12 '22 06:10

snarf


It doesn't use reflection when it doesn't need to. It just compare values bit by bit in case the struct if it can do so. However, there if any of the struct members (or members of members, any descendants) override object.Equals and provide its own implementation, obviously, it can't rely on bit-by-bit comparison to calculate the return value.

The reason it's slow is that the parameter to Equals is of type object and value types have to be boxed to be treated as an object. Boxing involves allocating memory on the heap and memory copying the value type to that location.

You could manually provide an overload for the Equals method that takes your own struct as parameter to prevent boxing:

public bool Equals(MyStruct obj) {
     return obj.i == i;
}
like image 45
mmx Avatar answered Oct 12 '22 04:10

mmx


The idea of a compiler generated function is justified.

Thinking about the effects however I think the language design team did it right. Compilergenerated methods known from C++ are hard to understand for beginners. Lets see what would happen in C# with autogenerated struct.Equals:

As it is now, the concept of .Equals() is simple:

  • Every struct inherits Equals from ValueType.
  • If overriden, the custom Equals method applies.

If the compiler would always create the Equals method, we could have:

  • Every struct inherits Equals from Object. (ValueType would no longer implement its own version)
  • Object.Equals is now always(!) overriden, either by the compiler generated Equals method or by the users implementation

Now our struct has an autogenerated override method that the code reader does not see! So how do you know that the base method Object.Equals does not apply to your struct? By learning all the cases of automatically compiler generated methods. And this is exactly one of the burdens learning C++.

Would consider it good decision to leave efficient struct Equals to the user and keep the concepts simple, requiring a standard default Equals method.

That said, performance critical structs should override Equals. The code below shows

3606 vs 53 Milliseconds measured on .Net 4.5.1

This performance gain is certainly due to avoiding virtual Equals, but anyway, so if the virtual Object.Equals would be called the gain would be much lower. Performance critical cases will not call Object.Equals however, so the gain here would apply.

using System;
using System.Diagnostics;

struct A
{
    public int X;
    public int Y;
}

struct B : IEquatable<B>
{
    public bool Equals(B other)
    {
        return this.X == other.X && this.Y == other.Y;
    }

    public override bool Equals(object obj)
    {
        return obj is B && Equals((B)obj);
    }

    public int X;
    public int Y;
}


class Program
{
    static void Main(string[] args)
    {
        var N = 100000000;

        A a = new A();
        a.X = 73;
        a.Y = 42;
        A aa = new A();
        a.X = 173;
        a.Y = 142;

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < N; i++)
        {
            if (a.Equals(aa))
            {
                Console.WriteLine("never ever");
            }
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);

        B b = new B();
        b.X = 73;
        b.Y = 42;
        B bb = new B();
        b.X = 173;
        b.Y = 142;

        sw = Stopwatch.StartNew();
        for (int i = 0; i < N; i++)
        {
            if (b.Equals(bb))
            {
                Console.WriteLine("never ever");
            }
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
    }
}

see also http://blog.martindoms.com/2011/01/03/c-tip-override-equals-on-value-types-for-better-performance/

like image 39
citykid Avatar answered Oct 12 '22 05:10

citykid