An abstract class is a class that cannot be instantiated, but can be inherited from; it is the ideal way to define a base class, that is the class that we inherit from. For example we could create an employee class that derives from the base class person.
abstract class Person { }
class Employee : Person { }
abstract class Person {
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Birthdate{ get; set; }
}
class Employee : Person { }
Meaning that we could very will do something like the following
we see that we can instantiate an employee with all the properties that are defined in it's base class Person.
Let's add two properties and function to our base class
now that we have a properties that return our Persons age, full name and a function that prints out a representation of the person class, let's implement our Employee class
Here we create a static runningId property, when a property, class, field, event or function are declared as static, they have three characteristics
next we created a read-only property Id, which is initialized to runningId and increments runningId by 1. the ++ syntax is the same as runningId = runningId + 1; it's just short hand, however it matters whether you use ++runningId or runningId++ in both cases runningId will be incremented by 1 the difference is that if the ++ precedes the field or property it's first incremented then assigned whereas if it follows the field or property it's assigned then incriminated.
We created another Print function in the Employee call in which we call the base implementation and append it with the employee number, notice the new keyword we use; this new keyword let's the compiler know that we on purpose have "improved" the base implementation of print. the nice thing is however that should we ever cast our Employee to a Person and call the Print method we'll call the Person version of it.
In the above code we'll first see the print method from the Employee class and then from the Person class.
Finally we declared a a double to store our employees wage and a virtual GetIncome function that takes in hours worked and returns the employees income for those hours.
The virtual keyword basically means that a subclass can but doesn't have to override our function, method, property, indexer or event declaration. We'll take a look at what this means in our implementation of our Manager class.
Our Manager class inherits from the Employee class and overrides the GetIncome function, the difference between override and new in this context is that when you override a function firstly it has to be declared as virtual in the base class and secondly it replaces the base implementation. That is if you cast a Manger as an Employee and call the GetIncome function you'd still get the Manager's implementation of it.
This doesn't cover all possible combinations of the keywords that we've covered, but hopefully increases your understanding of what they do and when to use them.
by combination here's what I mean
class Program
{
static void Main(string[] args)
{
var emp = new Employee
{
FirstName = "Pawel",
LastName = "Ciucias",
BirthDate = new DateTime(1984, 1, 31)
};
}
}
Let's add two properties and function to our base class
abstract class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public string FullName { get { return $"{FirstName} {LastName}"; } }
public int Age
{
get
{
var today = DateTime.Today;
var age = today.Year - BirthDate.Year;
return BirthDate > today.AddYears(-age) ? --age : age;
}
}
public string Print()
{
return $"{FullName} {BirthDate.ToShortDateString()}";
}
}
class Employee : Person
{
static int runningId = 0;
public int Id { get; } = ++runningId;
public double Wage { get; set; }
public new string Print() => $"{base.Print()} EmpId:{Id}";
public virtual double GetIncome(double hoursWorked) {
return hoursWorked * Wage;
}
}
- Can't be instantiated, it always exists; you can't instantiated it, it's always there.
- Shared between all of it's instances, in this case every instance of the employee class will share the same runningId field.
- Can't be referenced from an instance of their containing class, if runningId was public it would have to be referenced via Employee.runningId;
public int Id { get; } = ++runningId;
public new string Print() => $"{base.Print()} EmpId:{Id}";
static void Main(string[] args)
{
var emp = new Employee { FirstName = "Pawel", LastName = "Ciucias" ,
BirthDate = new DateTime(1984,1,31) };
Console.WriteLine(emp.Print());
Console.WriteLine(((Person)emp).Print());
}
public double Wage { get; set; }
public virtual string GetIncome(double hoursWorked) {
return (hoursWorked * Wage).ToString("C");
}
The virtual keyword basically means that a subclass can but doesn't have to override our function, method, property, indexer or event declaration. We'll take a look at what this means in our implementation of our Manager class.
class Manager : Employee {
public override string GetIncome(double hoursWorked)
{
return Wage.ToString("C");
}
}
class Program
{
static void Main(string[] args)
{
var emp = new Employee
{
FirstName = "Pawel",
LastName = "Ciucias",
BirthDate = new DateTime(1984, 1, 31),
Wage = 31.50
};
Console.WriteLine(emp.Print());
Console.WriteLine(((Person)emp).Print());
Console.WriteLine(emp.GetIncome(40));
var man = new Manager
{
FirstName = "Tomek",
LastName = "Ciucias",
BirthDate = new DateTime(1988, 8, 28),
Wage = 120000
};
Console.WriteLine(man.GetIncome(50));
Console.WriteLine(((Employee)man).GetIncome(50));
}
}
by combination here's what I mean
class Employee : Person
{
...
public virtual new string Print() => $"{base.Print()} EmpId:{Id}";
...
}
class Manager : Employee
{
...
public override string Print() => $"{FullName} EmpId:{Id} Salery:${Wage}";
...
}
here's a summary
abstract: can't be instantiated, but can be inherited; an abstract class must be a base class, a public abstract property or method or function can't contain a body or value and must be implemented in a subclass.
subclass: a class that inherits from a base class
superclass: the opposite of a sublcass, the class that is inherited from
static: only one instance that can't be instantiated it just exists for the duration of the application and is shared by everything.
new: in the context of inheritance when a property or function as is marked as new, you're letting the compiler now that on purpose you're improving a property or function in a subclass, if you cast that class back to it's base you'll be able to leverage that base class's implementation of the property or function.
virtual: allows you but doesn't commit you to "improve" via the new keyword or "replace" via the override keyword in a subclass.
override: can only be done on properties or methods that have been marked as virtual in a base class, overriding completely replaces the implementation of a virtual property or function, meaning that even if you cast to a base class you'll still only be able to access the subclass's version.
static: only one instance that can't be instantiated it just exists for the duration of the application and is shared by everything.
new: in the context of inheritance when a property or function as is marked as new, you're letting the compiler now that on purpose you're improving a property or function in a subclass, if you cast that class back to it's base you'll be able to leverage that base class's implementation of the property or function.
virtual: allows you but doesn't commit you to "improve" via the new keyword or "replace" via the override keyword in a subclass.
override: can only be done on properties or methods that have been marked as virtual in a base class, overriding completely replaces the implementation of a virtual property or function, meaning that even if you cast to a base class you'll still only be able to access the subclass's version.