Marcin Jahn | Dev Notebook
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects

Equality

Good Practices

Reference Types

  • Override Object’s virtual Equals(object o) method (check for: null, ReferenceEquals, fields/properties). For inherited types, first call base.Equals() and then check for added properties/fields
  • Implement == and != - to enable == and != (could use the Object’s static Equals, which will call the overridern Equals)
  • All of the above should give the same results
  • Implement Object.GetHashCode()

Implementing IEnumerable<T> is not recommended since it introduces inheritance problems (unless the class is sealed) and does not give any real benefits here.

Value Types

  • Implement IEquatable<T> - to avoid boxing and have type safety
  • Override Object’s virtual Equals(object o) method - to avoid reflection (just call the IEquatable<T> version)
  • Implement == and != - to enable == and != (just call the IEquatable<T> version)
  • All of the above should give the same results
  • Implement Object.GetHashCode()

Hash Codes

Hash codes are used in HashSets and Dictionaries to be able to retrieve elements efficiently. If two objects are equal they should have the same hash codes (or dictionaries might not work)! Therefore Equals and GetHashCode should rely on the same set of properties to get the result.

It’s recommended to implement hashcodes using XOR operator (^):

public override int GetHashCode()
{
return prop1.GetHashCode() ^ prop2.GetHashCode()
}

Object’s virtual Equals(object o) method

Classes

In classes it evaluates reference equality, unless overriden. String, tuples and delegate have it overriden.

Structs

In structs it compares values - all fields are compared (uses reflection). It’s recommended to override Equals(object o) by ourselves, because reflection has very bad performance.

It involves boxing in case of value types, which also is not a good thing. It’s a good practice to always override GetHashCode when overriding Equals.

Object’s static Equals(object o1, object o2) method

This method makes it safe to compare 2 objects if one of them is NULL. If they are not both NULL, the virtual Equals of o1 is used (as described above). So this method gives the same result as virtual one (only it protects from case where o1 is NULL).

It cannot be overriden (because it’s static)

Object’s ReferenceEquals(Object o) method

It checks only reference, it cannot be overriden (becasue it’s static). Useful in the case where some type overrides the virtual Equals, but also wants to check for reference equality.

IEquatable

It has the following method: Equals(T other) - it is strongly typed, which is good for value types (no boxing). All numeric primitive types implement it (Int, byte, …), other primitives not always. Some reference types implement it (i.e. String).

If some type implements it, we should make sure that both strong typed Equals and the one from Object return the same results.

It is not good for reference types:

  • there is no boxing, so no improvement in this area
  • doesn’t play nicely with inheritance

== operator

It’s a feature of C#, not .NET. If overriding Object’s Equals, == also should be overriden. == does not use Equals - it uses eqc IL procedure directly, which (?) uses hardware (unless overriden).

Tuple does not override ==, but overrides Object.Equals() - because of that operator only checks reference equality and can return different result than Object.Equals() - bad practice!

Implementing == requires implementing also != (by compiler).

Inheritance

== does not work well with inheritance - if we cast MyClass to Object and compare, it will use Object’s == implementation (reference equality). Equals methods don’t have this issue.

Generics

With generics, == ALWAYS compares references (even if T implements the operator). Object.Equals() should be used here!

Struct

For value types it has to be overloaded before using it.

Classes

For classes it compares the references (memory addresses), unless overriden.

Overloading

public static bool operator ==(MyClass left, MyClass right)
{
// Equality logic
}
public static bool operator !=(MyClass left, MyClass right)
{
// Inequality logic
}

The logic should be the same as in Object.Equals()!

IEqualityComparer

Requires Equals and GetHashCode to be implemented. It’s adviced to inherit from EqualityComparer<T> base class (similar as with Comparer<T>).

EqualityComparer is adviced to be a singleton.

It’s problematic to deal with derived types - as exmplained in my notes on comparers.

EqualityComparer<T>.Default - every type can use the default comparer, which uses the T’s equality implementation.

StringComparer is a static class that has 6 variosu comparers for strings (implements IComparer and IEqualityComparer).

IStructuralEquatable

Collections are structurally equal if:

  • they contain same elements
  • elements are in the same order

Arrays and tuples implement it explicitly - in order to use it, we need to cast an array to IStructuralEquatable and use Equals It’s not generic.

Other Notes

In .NET NULL always equals NULL, i.e.: Object.Equals(null, null) returns TRUE.

←  TAP Tips
Comparisons  →
© 2023 Marcin Jahn | Dev Notebook | All Rights Reserved. | Built with Astro.