Understanding Base Class / Derive Class Casting Rules
static void CastingExamples()
{
// A manager "is-an" object
object frank = new Manager();
// A manager "is-an" Employee too.
Employee moon = new Manager();
}
It's always safe to store a derived object within a base class reference. Formally, this is called an "implicit cast", as "it just works" given the laws of inheritance.
static void GivePromotion(Employee emp)
{
Console.WriteLine(emp.Name);
}
GivePromotion(moon); // OK
GivePromotion(frank); // Error
Even though you can figure out that the object reference is pointing to an Employee compatible class in memory,the compiler cannot, as that will not be known until runtime. You can satisfy the compiler by performing an explicit cast. This is the second law of casting: you can explicitly downcast using the C# casting operator.
(ClassIWantToCastTo)referenceIHave
GivePromotion((Employee)frank);Using the C# as Keyword
Be aware that explicit casting is evaluated at runtime, not compile time.
C# provides that as keyword to quickly determine at runtime whether a given type is compatible with another. When you use the "as" keyword, you are able to determine compatibility by checking against a null return value.
object[] things = new object[4];
things[0] = new Hexagon();
things[1] = false;
things[2] = new Manager();
things[3] = "Last Thing";
foreach(object item in things)
{
Hexagon h = item as Hexagon;
if (h == null)
{
Console.WriteLine("Not hexagon");
}else
{
h.Draw();
}
}Using the C# is Keyword
C# provides the is keyword to determine whether 2 items are compatible. Unlike the as keyword, the is keyword return false, rather than a null reference, if the types are incompatible.
New in C# 7.0, the is keyword can also assign the converted type to a variable if the cast works.
static void GivePromotion(Employee emp)
{
if (emp is SalesPerson)
{
Console.WriteLine(((SalesPerson)emp).Pay);
}else if (emp is Manager)
{
Console.WriteLine(emp.Name);
}
}
C# 9.0 introduced additional pattern matching.
if (emp is not Manager and not SalesPerson)
{
}Discards with the is Keyword
The is keyword can also be used in conjunction with the discard variable placeholder.
if (obj is var _) { }
if (emp is SalesPerson s) { }
else if (emp is Manager m) { }
else if (emp is var _) { } // This will catch any Employee instance that is not a Manager,SalesPerson.Understanding the Super Parent Class System.Object
In the .NET Core universe, every type ultimately derives from a base class named System.Object, which can be represented by the C# object keyword. The object class defines a set of common member for every type in the framework. In fact, when you do build a class that does not explicitly define its parent, the compiler automatically derives your type from object.
class Car : object { }
Like any class, System.object defines a set of members.
public class Object
{
// Virtual members.
public virtual bool Equals(object obj);
protected virtual void Finalize();
public virtual int GetHashCode();
public virtual string ToString();
// Instance-level, nonvirtual members.
public Type GetType();
protected object MemberwiseClone();
// Static members.
public static bool Equals(object objA, object objB);
public static bool ReferenceEquals(object objA, object objB);
}- Equals() : By default, this method returns true only if the items being compared refer to the same item in memory. Thus, Equals() used to compare object references, not the state of the object. Be aware that if you override Equals(), you should also override GetHashCode(), as these methods are used internally by Hashtable types to retrieve subjects from the container.
- Finalize() : For the time being, you can understand this method (when overridden) is called to free any allocated resources before the object is destroyed.
- GetHashCode(): This method returns an int that identifies a specific object instance.
- ToString(): This method returns a string representation of this object using the <namespace>.<type_name> format (termed the fully qualified name).
- GetType(): This method returns a Type object that fully describes the object you are currently referencing. In short, this is a runtime type identification (RTTI) method available to all objects.
- MemberwiseClone(): This method exists to return a member-by-member copy of the current object, which is often used when cloning an object.
Overriding System.Object.ToString() and System.Object.Equals()
class Person
{
public string FirstName { get; set; }
public override string ToString()
{
return $"First Name {FirstName}";
}
public override bool Equals(object? obj)
{
if (!(obj is Person temp))
{
return false;
}
if (temp.FirstName == this.FirstName)
{
return true;
}
return false;
}
}Overriding System.Object.GetHashCode()
A has code is a numerical value that represents an object as a particular state. For ex, 2 string variables that hold the value Hello, you will obtain the same hash code. However if one of the string objects were in lower case (hello), you would obtain different hash codes.
By default, System.Object.GetHashCode() uses your object's current location in memory to yield the has value. However, if you are building a custom type that you intend to store in a hashTable type (within the System.Collections namespace), you should always override this member, as the HashTable will be internally invoking Equals() and GetHashCode() to retrieve the correct object.
Note: To be more specific System.Collections.Hashtable class calls GetHashCode() internally to gain a general idea where the object is located, but a subsequent (internal) call to Equals() determines the exact match.