Skip to content

Latest commit

 

History

History
315 lines (222 loc) · 12.5 KB

object.md

File metadata and controls

315 lines (222 loc) · 12.5 KB

object, dynamic, overriding GetHashCode and Equals

Table of contents

object

The object keyword is an alias for System.Object type, therefore System.Object and object are equivalent.

In C# type system all types, predefined and user-defined, reference types and value types, inherit directly or indirectly from System.Object.

An object is a block of memory that has been allocated and configured according to the definition of its type.

Methods

Equals

The Equals method determines whether two object instances are equal.

↑ C# difference between == and Equals().

Finalize

The Finalize method allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage collection.

GetHashCode

The GetHashCode method provides a hash code for algorithms that need quick checks of object equality.

public virtual int GetHashCode() => RuntimeHelpers.GetHashCode(this);

A hash code is a numeric value that is used to insert and identify an object in a hash-based collection, such as the Dictionary<TKey,TValue> class, the Hashtable class, or a type derived from the DictionaryBase class.

Two objects that are equal return hash codes that are equal. However, the reverse is not true: equal hash codes do not imply object equality, because different (unequal) objects can have identical hash codes.

If you override the GetHashCode method, you should also override Equals, and vice versa. If your overridden Equals method returns true when two objects are tested for equality, your overridden GetHashCode method must return the same value for the two objects.

The GetHashCode method can be overridden by a derived type. If GetHashCode is not overridden, hash codes for reference types are computed by calling theObject.GetHashCode method of the base class, which computes a hash code based on an object's reference. In other words, two objects for which the ReferenceEquals method returns true have identical hash codes.

If value types do not override GetHashCode, the ValueType.GetHashCode method of the base class uses reflection to compute the hash code based on the values of the type's fields. In other words, value types whose fields have equal values have equal hash codes.

GetType

The GetType method gets the ↑ type declaration of the current instance.

MemberwiseClone

The MemberwiseClone method creates a shallow copy of the current object.

protected object MemberwiseClone ();

Example of usage:

A original = new A
{
    Code = 1,
    B = new B
    {
        Code = 1,
        C = new C
        {
            Code = 1
        }
    }
};

Console.WriteLine($"{original.Code}, {original.B.Code}, {original.B.C.Code}"); // 1, 1, 1

A copy = original.Clone();
Console.WriteLine($"{copy.Code}, {copy.B.Code}, {copy.B.C.Code}"); // 1, 1, 1

copy.Code = 2;
copy.B.Code = 3;
Console.WriteLine($"{original.Code}, {copy.Code}, {original.B.Code}, {copy.B.Code}"); // 1, 2, 3, 3

copy.B.C.Code = 4;
Console.WriteLine($"{original.B.C.Code}, {copy.B.C.Code}"); // 4, 4

class A
{
    public int Code { get; set; }

    public B B { get; set; }

    public A Clone()
    {
        return (A) MemberwiseClone();
    }
}

class B
{
    public int Code { get; set; }
    public C C { get; set; }
}

class C
{
    public int Code { get; set; }
}

ReferenceEquals

The ReferenceEquals method determines whether the specified object instances are the same instance.

public static bool ReferenceEquals(object? objA, object? objB) => objA == objB;

Unlike the Equals method and the equality operator, the ReferenceEquals method cannot be overridden. Because of this, if you want to test two object references for equality and you are unsure about the implementation of the Equals method, you can call the ReferenceEquals method.

ToString

The ToString method returns a string that represents the current object.

public virtual string? ToString() => this.GetType().ToString();

dynamic

The dynamic type is a static type, but an object of type dynamic bypasses static type checking.

C# is a ↑ statically-typed, ↑ strongly-typed language.

In most cases, an object of dynamic type functions like it has type object. The dynamic type differs from object in that operations that contain expressions of type dynamic aren't resolved or type checked by the compiler. The compiler packages together information about the operation, and that information is later used to evaluate the operation at run time. As part of the process, variables of type dynamic are compiled into variables of type object. Therefore, type dynamic exists only at compile time, not at run time.

The compiler assumes a dynamic element supports any operation. Therefore, you don't have to determine whether the object gets its value from a ↑ COM API, from a dynamic language such as ↑ IronPython, from the HTML Document Object Model, DOM, from reflection, or from somewhere else in the program. However, if the code isn't valid, errors surface at run time.

↑ Using type dynamic.

Overriding GetHashCode and Equals

When two objects are equal they must return the same hash code, but reverse is not necessary: two object may return the same hash code, but they can be not equal.

For more information about overriding GetHashCode, see the ↑ "Notes to Inheritors" section.

Dictionary

If you override Equals method you must also override GetHashCode method, because if you don't than two objects which are in fact equal will likely end up residing in different buckets inside of Dictionary<TKey,TValue> of HashSet<T>. Therefore you will have multiple equal keys in your dictionary or hash table.

Below GetHashCode will be called twice: right before a1 and a2 are added into the dictionary; and Equals will be called once: before a2 is added:

A a1 = new();
A a2 = new();

var dict = new Dictionary<A, string>();

dict.Add(a1, "one");
dict.Add(a2, "two");

Console.WriteLine("End");

class A
{

    public override bool Equals(object? obj)
    {
        return false;
    }

    public override int GetHashCode()
    {
        return 12345;
    }
}

Before adding a new item to the dictionary the GetHashCode method of the item's key object is called to determine bucket's index of the hash table that backs up the dictionary. Equals is called on the item's key object only if there is an item already in the bucket that corresponds to the int returned by GetHashCode, and it's called as many times as many items are already in that bucket.

Now Equals returns always true. If you would make it to always return false, then the code above will throw "An item with the same key has already been added" exception. One way to avoid this exception would be to use dict[a2] = "two" instead of dict.Add(a2, "two"), the former will overwrite existing value if key already exists in the dictionary.

Now if you will try to run this code:

string str1 = dict[a1];

first it will call GetHashCode on a1 key to figure out bucket's index. Then it will call Equals on a1's and a2's keys that are already in the bucket. Since Equals returns always false, then it will raise "The given key 'A' was not present in the dictionary" exception.

Also take a look at HashCode.Combine method that combines several values into a single hash code.

Examples

Class

var user1 = new User("Bob", new DateOnly(1990, 10, 5));
var user2 = new User("Bob", new DateOnly(2000, 10, 5));
var user3 = new User("Bob", new DateOnly(1990, 10, 5));

Console.WriteLine(user1.GetHashCode()); // 2007968221
Console.WriteLine(user2.GetHashCode()); // -2132409190
Console.WriteLine(user3.GetHashCode()); // 2007968221

Console.WriteLine(user1 == user3); // True
Console.WriteLine(Equals(user1, user3)); // True
Console.WriteLine(ReferenceEquals(user1, user3)); // False

class User
{
    public User(string name, DateOnly dateOfBirth)
    {
        Name = name;
        DateOfBirth = dateOfBirth;
    }

    public string Name { get; init; }
    public DateOnly DateOfBirth { get; init; }

    public override bool Equals(object? obj)
    {
        return obj is User other && Equals(other);
    }

    private bool Equals(User other)
    {
        return Name == other.Name && DateOfBirth.Equals(other.DateOfBirth);
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Name, DateOfBirth);
    }
    
    public static bool operator ==(User left, User right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(User left, User right)
    {
        return !left.Equals(right);
    }
}

Record

var customer1 = new Customer("John", new DateOnly(1990, 10, 5));
var customer2 = new Customer("John", new DateOnly(1990, 10, 5));

Console.WriteLine(customer1.GetHashCode()); // 802341848
Console.WriteLine(customer2.GetHashCode()); // 802341848

Console.WriteLine(customer1 == customer2); // True
Console.WriteLine(customer1.Equals(customer2)); // True
Console.WriteLine(ReferenceEquals(customer1, customer2)); // False

record Customer(string Name, DateOnly BirthDate);

Structure

var user1 = new User("Bob", new DateOnly(1990, 10, 5));
var user2 = new User("Bob", new DateOnly(2000, 10, 5));
var user3 = new User("Bob", new DateOnly(1990, 10, 5));

Console.WriteLine(user1.GetHashCode()); // 1075247716
Console.WriteLine(user2.GetHashCode()); // 1075247716
Console.WriteLine(user3.GetHashCode()); // 1075247716

Console.WriteLine(user1 == user3); // True
Console.WriteLine(Equals(user1, user3)); // True
Console.WriteLine(ReferenceEquals(user1, user3)); // False. Warning: 'Object.ReferenceEquals' is always false because it is called with value type

struct User
{
    public User(string name, DateOnly dateOfBirth)
    {
        Name = name;
        DateOfBirth = dateOfBirth;
    }

    public string Name { get; init; }
    public DateOnly DateOfBirth { get; init; }

    public static bool operator ==(User left, User right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(User left, User right)
    {
        return !left.Equals(right);
    }
}