Understanding the Basic Mechanics of Inheritance
When you establish "is-a" relationships between classes, you are building a dependency between 2 or more class types.
class Car
{
public readonly int MaxSpeed;
private int _currSpeed;
public Car(int max)
{
MaxSpeed = max;
}
public Car()
{
MaxSpeed = 55;
}
public int Speed
{
get => _currSpeed;
set => _currSpeed = MaxSpeed;
}
}Specifying the Parent Class of an Existing Class
The "is-a" relationship (formally termed classical inheritance) allows you to build new class definitions that extend the functionality of an existing class.
The existing class that will serve as the basics for the new class termed a "base class, superclass, or parent class". The role of a base class is to define all the common data and members for the classes that extend it. The extending classes are formally termed derived or child classes".
In C#, you make use of the colon operator on the class definition to establish an "is-a" relationship between classes.
class MiniVan : Car
{
}Regarding Multiple Base Classes
It's important to keep in mind that C# demands that a given class have exactly one direct base class. It's not possible to create a class type that directly derives from 2 or more base classes (this technique, which is supported in unmanaged C++, is known as multiple inheritance, or simply MI).
Using the sealed Keyword
sealed, that prevents inheritance from occuring. When you mark a class as sealed, the compiler will not allow you to derive from this type.
sealed class MiniVan : Car
{
}If you were to attempt to derive from this class, you would receive a compile-time error.
class DeluxeMiniVan : MiniVan { } // ErrorNote: C# structures are always implictly sealed.
Understanding the Second Pillar of OOP : The Details of Inheritance
class Employee
{
public string Name;
public int Age;
public float Pay;
public Employee()
{
}
public Employee(string name,int age, float pay)
{
Name = name;
Age = age;
Pay = pay;
}
}
class Manager : Employee
{
public int StockOptions { get; set; }
}
class SalesPerson : Employee
{
public int SalesNumber { get; set; }
}Calling Base Class Constructors with the base Keyword
The "base" keyword is hanging off the constructor signature (much like the syntax used to chain constructors on a single class using the this keyword), which always indicates a derived constructor is passing data to the immediate parent constructor.
class Manager : Employee
{
public int StockOptions { get; set; }
public Manager()
{
}
public Manager(string name,
int age,
float pay,
int stock) : base(name,age,pay)
{
StockOptions = stock;
}
}
Manager m = new Manager("Alice", 45, 90000, 580);
Keeping Family Secrets
When a base class defines protected data or protected members, it establishes a set of items that can be accessed directly by any descendant.
class Employee
{
protected string EmpName;
protected int EmpId;
}
Note: Convention is that protected members are named PascalCased (EmpName). Not underscore_camelCase (_empName)
Understanding Inheritance with Record Types
The new C# 9.0 record types also supports inheritance.
Inheritance for Record Types with Standard Properties
public record Car
{
public string Make { get; init; }
public Car(string make)
{
Make = make;
}
}
public sealed record MiniVan : Car
{
public int Seating { get; init; }
public MiniVan(string make,int seating) : base(make)
{
Seating = seating;
}
}
// No difference with classes and records in inheritance
Inheritance for Record Types with Positional Parameters
The derived record declares positional parameters for all of the parameters in the base record. The derived record doesn't hide them but uses them from the base record. The derived record only created and initializes properties that are not the base record.
public record PositionalCar(string Make);
public record PositionalMiniVan(string Make, int seating) : PositionalCar(Make);
public record Motorcycle(string Make, string Model);
public record Scooter(string Make, string Model) : Motorcycle(Make,Model);
public record FancyScooter(string Make, string Model, string FancyColor) : Scooter(Make, Model);Nondestructive Mutation with Inherited Record Types
When creating new record type instances using "with" expression, the resulting record type is the same runtime type of the operand.
Motorcycle mc = new FancyScooter("H","1","Gold");
Console.WriteLine($"{mc is FancyScooter}");
Motorcycle mc2 = mc with {Make="H",Model="L"};
Console.WriteLine($"{mc2 is FancyScooter}");Deconstructor Behavior with Inherited Record Types
The Deconstruct() method of a derived record returns the values of all positional properties of the declared, compile-time type.
If the variable is cast to the derived type, then all of the positional properties of the derived type are deconstructed.
var (make2, model2, fancyColor2) = (FancyScooter)mc;Programming for Containment/Delegation
"has-a" relationship (also known as the containment/delegation model or aggregation).
class BenefitPackage
{
public double ComputerPayDeduction()
{
return 125.0;
}
}
class Employee
{
protected BenefitPackage EmpBenefits = new BenefitPackage(); // has-a, contains a benefit package object.
}
Exposing the functionality of the contained object to the outside world required delegation. Delegation is simply the act of adding public members to the containing class that use the contained object's functionality.
partial class Employee
{
// benefit object
protected BenefitPackage EmpBenefits = new BenefitPackage();
// expose certain benefits behaviors of object.
public double GetBenefitCost()
{
return EmpBenefits.ComputerPayDeduction();
}
// Expose object through a custom prop.
public BenefitPackage Benefits
{
get
{
return EmpBenefits;
}
set
{
EmpBenefits = value;
}
}
}
Understanding Nested Type Definitions
In C#, it is possible to define a type (enum,class,interface,struct,or delegate) directly within the scope of a class or structure.
When you have done so, the nested (or "inner") type is considered a member of the nesting (or "outer") class and in the eyes of the runtime can be manipulated like any other member (fields,properties,methods and events).
public class OuterClass
{
public class PublicInnerClass { }
private class PrivateInnerClass { }
}- Nested types allow you to gain complete control over the access level of the inner type because they may be declared privately.
- Because a nested type is a member of the containing class, it can access private members of the containing class.
- Often, a nested type is useful only as a helper for the outer class and is not intended for use by the outside world.
OuterClass.PublicInnerClass inner; // OK!
inner = new OuterClass.PublicInnerClass();
OuterClass.PrivateInnerClass inner2; //Error!
inner2 = new OuterClass.PrivateInnerClass();