10

I came across an extension method that applies to structs (SomeStruct) and returns whether or not the value is equal to the default(SomeStruct) (when the parameterless constructor is called).

public static bool IsDefault<T> (this T value)
    where T : struct
{
    return (!EqualityComparer<T>.Default.Equals(value, default(T)));
}

This got me wondering whether the struct was being boxed. This is purely out of curiosity as there are pros/cons to boxing/passing by value depending on the context.

Assumptions:

  1. The first of the following methods is illegal since structs do not implicitly override the equality operators ==/!=.
  2. The second "appears" to avoid boxing.
  3. The third method should always box the struct since it's calling object.Equals(object o).
  4. The fourth has both overloads available (object/T) so I'm assuming it will avoid boxing as well. However, the target struct would need to implement the IEquatable<T> interface, making the helper extension method not very helpful.

Variations:

public static bool IsDefault<T> (this T value)
    where T : struct
{
    // Illegal since there is no way to know whether T implements the ==/!= operators.
    return (value == default(T));
}

public static bool IsDefault<T> (this T value)
    where T : struct
{
    return (!EqualityComparer<T>.Default.Equals(value, default(T)));
}

public static bool IsDefault<T> (this T value)
    where T : struct
{
    return (value.Equals(default(T)));
}

public static bool IsDefault<T> (this T value)
    where T : struct, IEquatable<T>
{
    return (value.Equals(default(T)));
}

This question is about confirming the above assumptions and if I am misunderstanding and/or leaving something out.

vero
  • 243
  • 1
  • 4
  • 12
Raheel Khan
  • 14,205
  • 13
  • 80
  • 168

2 Answers2

6
  1. The first of the following methods is illegal since structs do not implicitly override the equality operators ==/!=.

True.

  1. The second "appears" to avoid boxing.

The signature of the called method is EqualityComparer<T>.Equals(T,T) which uses the type T for the parameters, so it does not require boxing to call.

The implementation of the default comparer checks if T is IEquatable<T> and if so uses a comparer that uses IEquatable<T>.Equals and else uses a comparer for Object.Equals, so internally there might be boxing applied if the struct is not IEquatable ('only if needed').

  1. The third method should always box the struct since it's calling object.Equals(object o).

True.

  1. The fourth has both overloads available (object/T) so I'm assuming it will avoid boxing as well. However, the target struct would need to implement the IEquatable interface, making the helper extension method not very helpful.

Yes, it does not require boxing, as per this SO answer. This is the effective code you get for the specific case of T : IEquatable from the EqualityComparer<T>.Default.

Community
  • 1
  • 1
MicroVirus
  • 5,324
  • 2
  • 28
  • 53
  • Thanks. So I understand that the second case WILL require boxing if it does NOT implement IEquatable? If so, none of the approaches an unboxed operation without the consumer (creator of the struct) implementing an interface (doing more work just to consume our library). – Raheel Khan Apr 24 '16 at 13:16
  • 2
    Boxing costs are very low, especially compared to a typical object compare, so I wouldn't worry about it in practical terms. If the creator of a struct doesn't implement equality, then it defers to the default equality given by `Object.Equals`, so then boxing is unavoidable, but also won't make a difference compared to the (byte-comparison/reflection) cost of `Object.Equals`. – MicroVirus Apr 24 '16 at 13:44
  • Perf and costs are very subjective. I've come across scenarios where boxing costs far outweigh even the cost of `auto public properties`. In my case, I had to implement `public readonly` fields for a `Vector2` struct which brought about a ridiculous perf improvement over properties with backing fields. But these are specialized scenarios where the developer would already know enough to provide relevant built-in comparisons. Aside from that, could you comment on: "the second case WILL require boxing if it does NOT implement IEquatable?". – Raheel Khan Apr 24 '16 at 14:14
  • 2
    Yes, it will. However if the structure does not implement IEquateable there is no way to test equity without boxing. – Scott Chamberlain Apr 24 '16 at 14:22
  • 1
    @RaheelKhan Like Scott says, if the struct does not implement `IEquatable` then your only choice is to call `Object.Equals` (directly or indirectly), so boxing is going to happen and there's nothing you can do about it. – MicroVirus Apr 24 '16 at 14:46
  • Thanks. Was looking to cement that. No avoiding `Object.Equeals` without the explicit interface. – Raheel Khan Apr 24 '16 at 17:01
2

Let me add that for structs, if you do not define comparations, the details get complicated.

For example How to define value equality for a type says:

Any struct that you define already has a default implementation of value equality that it inherits from the System.ValueType override of the Object.Equals(Object) method. This implementation uses reflection to examine all the fields and properties in the type. Although this implementation produces correct results, it is relatively slow compared to a custom implementation that you write specifically for the type.

See also Performance implications of default struct equality in C# for more details, including:

There is an optimized default version for Equals and GetHashCode but you should never rely on it because you may stop hitting it with an innocent code change.

Pablo H
  • 609
  • 4
  • 22