If c# struct is a value type why does default Equals need to use reflection?

273 Views Asked by At

I always thought that C# structs being value types meant that a comparison would be something like checking two blocks of memory are the same, but I just read https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type#struct-example and realized I'm mistaken.

For structs, the default implementation of Object.Equals(Object) (which is the overridden version in System.ValueType) performs a value equality check by using reflection to compare the values of every field in the type. When an implementer overrides the virtual Equals method in a struct, the purpose is to provide a more efficient means of performing the value equality check and optionally to base the comparison on some subset of the struct's fields or properties.

What is the reason the default Equals implementation of a C# struct needs to use field by field reflection as opposed to a straight value comparison of their data?

2

There are 2 best solutions below

0
Ilian On

Consider structs that have members that are class-types and have overriden their Equals methods. For example:

internal struct Struct
{
    public string Str { get; set; }
}

internal class Program
{
    public static void Main(string[] args)
    {
        var sb1 = new StringBuilder("Hello");
        var sb2 = new StringBuilder("Hello");
        var str1 = sb1.ToString();
        var str2 = sb2.ToString();

        // Different string instances but equal in value
        Debug.Assert(!ReferenceEquals(str1, str2));

        var struct1 = new Struct { Str = str1 };
        var struct2 = new Struct { Str = str2 };

        // Should be equal since the strings are equal in value
        Debug.Assert(struct1.Equals(struct2));
    }
}

Without reflection, the above would not work.

0
Charlieface On

The answer is simple: ValueType.Equals has no idea what fields are in the struct unless it uses boxing and reflection on it, which is very inefficient. It can't directly access the fields without doing some wacky unsafe trickery.

Suppose you could write a base class for a struct yourself (you can't, but let us imagine). Bear in mind that System.ValueType is a class therefore references to it automatically box.

abstract class MyValueType
{
    bool Equals(object o)
    {
        if (o is null || o.GetType() != this.GetType())
            return false;

        if ( // what are you going to put here???

    }
}

The only way to write this is using reflection. Which is why it's recommended to override it with your own more efficient implementation, and why record struct automatically does it for you.