Skip to main content

C# Equality Operator Overloading

Here’s a good example for how to implement operator for equality (==, !=, .Equals()).

This is taken from the Version3 type in OGA.SharedKernel.Lib.

NOTE: These overrides include method signatures for older and newer NET Framework versions.

Equal / Not Equal (==, !=, Equals())

This first block is for equality and inequality overloading.

        #region Operator Overloads

        /// <summary>
        /// Implements the IEquatable interface, same as the native Version class.
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        // Force inline as the true/false ternary takes it above ALWAYS_INLINE size even though the asm ends up smaller
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#if (NET452 || NET47 || NET48)
        public static bool operator ==(cVersion3 v1, cVersion3 v2)
#else
        public static bool operator ==(cVersion3? v1, cVersion3? v2)
#endif
        {
            // Test "right" first to allow branch elimination when inlined for null checks (== null)
            // so it can become a simple test
            if (v2 is null)
            {
                // return true/false not the test result https://github.com/dotnet/runtime/issues/4207
                return (v1 is null) ? true : false;
            }

            // Quick reference equality test prior to calling the virtual Equality
            return ReferenceEquals(v2, v1) ? true : v2.Equals(v1);
        }

#if (NET452 || NET47 || NET48)
        /// <summary>
        /// Implements the IEquatable interface, same as the native Version class.
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator !=(cVersion3 v1, cVersion3 v2) => !(v1 == v2);
#else
        /// <summary>
        /// Implements the IEquatable interface, same as the native Version class.
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator !=(cVersion3? v1, cVersion3? v2) => !(v1 == v2);
#endif

        #endregion

The above equality logic requires an override of Equals().
Here’s what that looks like:

NOTE: The virtual Equals() method that we override accepts a type of Object.
So, we include an override for that, and a type-specific public overload of the same method name that does the work for both.

#if (NET452 || NET47 || NET48)
        /// <summary>
        /// Implementation of the IEquatable interface.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
#else
        /// <summary>
        /// Implementation of the IEquatable interface.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals([NotNullWhen(true)] object? obj)
#endif
        {
            return Equals(obj as cVersion3);
        }

#if (NET452 || NET47 || NET48)
        /// <summary>
        /// Implementation of the IEquatable interface.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public bool Equals(cVersion3 obj)
#else
        /// <summary>
        /// Implementation of the IEquatable interface.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public bool Equals([NotNullWhen(true)] cVersion3? obj)
#endif
        {
            return object.ReferenceEquals(obj, this) ||
                (!(obj is null) &&
                _Major == obj._Major &&
                _Minor == obj._Minor &&
                _Patch == obj._Patch);
        }

Comparison Overloading (>, <, >=, <=)

And, this block is for greater and less than overloading:

        #region Operator Overloads

#if (NET452 || NET47 || NET48)
        /// <summary>
        /// Implements the IEquatable interface, same as the native Version class.
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator <(cVersion3 v1, cVersion3 v2)
#else
        /// <summary>
        /// Implements the IEquatable interface, same as the native Version class.
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator <(cVersion3? v1, cVersion3? v2)
#endif
        {
            if (v1 is null)
            {
                return !(v2 is null);
            }

            return v1.CompareTo(v2) < 0;
        }

#if (NET452 || NET47 || NET48)
        /// <summary>
        /// Implements the IEquatable interface, same as the native Version class.
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator <=(cVersion3 v1, cVersion3 v2)
#else
        /// <summary>
        /// Implements the IEquatable interface, same as the native Version class.
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator <=(cVersion3? v1, cVersion3? v2)
#endif
        {
            if (v1 is null)
            {
                return true;
            }

            return v1.CompareTo(v2) <= 0;
        }

#if (NET452 || NET47 || NET48)
        /// <summary>
        /// Implements the IEquatable interface, same as the native Version class.
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator >(cVersion3 v1, cVersion3 v2) => v2 < v1;
#else
        /// <summary>
        /// Implements the IEquatable interface, same as the native Version class.
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator >(cVersion3? v1, cVersion3? v2) => v2 < v1;
#endif

#if (NET452 || NET47 || NET48)
        /// <summary>
        /// Implements the IEquatable interface, same as the native Version class.
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator >=(cVersion3 v1, cVersion3 v2) => v2 <= v1;
#else
        /// <summary>
        /// Implements the IEquatable interface, same as the native Version class.
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator >=(cVersion3? v1, cVersion3? v2) => v2 <= v1;
#endif

        #endregion

NOTE: The above comparison overloads requires a type-specific CompareTo() implementation override.

So, here’s one that you can work from:

NOTE: The virtual CompareTo() method that we override accepts a type of Object.
So, we include an override for that, and a type-specific public overload of the same method name that does the work for both.

#if (NET452 || NET47 || NET48)
        /// <summary>
        /// Implementation of the IComparable interface.
        /// </summary>
        /// <param name="version"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        public int CompareTo(object version)
#else
        /// <summary>
        /// Implementation of the IComparable interface.
        /// </summary>
        /// <param name="version"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        public int CompareTo(object? version)
#endif
        {
            if (version == null)
            {
                return 1;
            }

            if (version is cVersion3 v)
            {
                return CompareTo(v);
            }

            throw new ArgumentException("Invalid Version Instance.");
        }

#if (NET452 || NET47 || NET48)
        /// <summary>
        /// Implementation of the IComparable interface.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public int CompareTo(cVersion3 value)
#else
        /// <summary>
        /// Implementation of the IComparable interface.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public int CompareTo(cVersion3? value)
#endif
        {
            if (value == null)
            {
                return 1;
            }

            return
                object.ReferenceEquals(value, this) ? 0 :
                value is null ? 1 :
                _Major != value._Major ? (_Major > value._Major ? 1 : -1) :
                _Minor != value._Minor ? (_Minor > value._Minor ? 1 : -1) :
                _Patch != value._Patch ? (_Patch > value._Patch ? 1 : -1) :
                0;
        }