Inheritance, Interfaces, and the Abstract, Virtual, and Sealed Keywords in C#
Introduction
This is meant to be an incomplete overview of inheritance related keywords in C#. There are more details in the MSDN documentation.
- abstract: https://msdn.microsoft.com/en-us/library/sf985hc5.aspx
- virtual: https://msdn.microsoft.com/EN-US/library/9fkccyh4%28v=VS.120,d=hv.2%29.aspx
- sealed: https://msdn.microsoft.com/en-us/library/88c54tsw.aspx
- interface: https://msdn.microsoft.com/EN-US/library/87d83y5b%28v=VS.120,d=hv.2%29.aspx
- abstract and sealed: https://msdn.microsoft.com/EN-US/library/ms173150%28v=VS.120,d=hv.2%29.aspx
Remaining Questions to Answer
This post is a work in progress. I would still like to document answers to the following questions.
- Are an interface's methods implicitly virtual (in the same way that abstract methods are implicitly virtual?)
- If not, can an interface's methods contain the virtual keyword?
- Is there any point in sealing a method that wasn't previously virtual?
- What is an indexer?
Use Cases of Interface, Abstract, Virtual and Sealed
Inheritance is about sharing the inputs and outputs (the what) and/or the transformation and side-effects (how) among classes and interfaces. A method's signature defines the what and its body defines the how.
Containers
Interface
Provide what; require how. Use an interface 1. to provide inputs and outputs that a derived class must process and 2. to require that the derived class define how to process those. Analogy: a recipe book template that provides dish names, ingredients, and resultant meals but no recipe steps.
Abstract Class
Provides what; require, suggest, or provide how. An abstract class is versatile: 1. use it with abstract members to provide the what and require the how; 2. use it with virtual members to provide the what and suggest the how, or 3. use it with normal members to provide both the what and the how. You can take all three approaches in the same class. Analogy: The same cookbook template as above but also with penned in and/or penciled in recipe steps.
Normal Class
Provide what; suggest or provide how. 1. Use it with virtual members to suggest processing, 2 use it with normal members to provide processing. You can can both approaches in the same class. Abstract members though are forbidden.
Sealed Class
Prevent further derivations; end the inheritance line. Use it to prevent further derived classes. In other words, end this branch of the inheritance tree.
Members
Abstract Member
Provide what; require how. Use an abstract member, within an abstract class, to provide inputs and outputs that a derived class must process.
Virtual Member
Provide what; suggest how. Use a virtual member to provide the what and suggest the how. Any descendant class can override the how. Analogy: a suggested, default way to make toast out of bread and a toaster.
Normal Member
Provide both what and how. Use a normal, non-virtual member to provide both the what and how that a derived class cannot override. Analogy: a mandatory set of steps for how to make cereal.
Sealed Member
Override how; prevent further overriding. Use a member to prevent further derived classes from changing how this member works. Analogy: a mandatory set of steps for what used to be a suggested way to make toast.
Continuum of Member Implementation Freedom
Minimal Freedom --> Most Freedom --> Minimal Freedom. A derived class...
- ... must implement all members of an interface,
- ... must implement (override) all abstract members,
- ... may implement (override) any virtual members,
- ... may no longer implement (override) sealed members, and
- ... may not implement (override) normal members.
Sealed is only appropriate for a member that is virtual somewhere up the inheritance chain. Once a member is virtual, that member stays virtual until sealed. Sealed says that no further descendant classes can override this.
Interface Versus Abstract Class
Similarities
- Both interfaces and abstract classes may contain method signatures that lack implementation details.
Differences
- a derived class can implement multiple interfaces whereas a derived class can implement only one abstract class
- a derived class must implement all of an interfaces methods whereas a derived class must implement only the abstract members of an abstract class
- an interface cannot have any implementation details whereas an abstract class can contain implementation details
Abstract Versus Virtual
Similarities
Both the abstract and virtual keywords...
- ... are appropriate for methods, properties, indexers, and events
- ... allow a derived class to override the member with the override modifier
- ... let each subsequent descendant re-override the previous descendants override (until a member is sealed)
Differences
- abstract can apply to a class whereas virtual cannot apply to a class
- derived classes must override abstract members whereas derived classes may override virtual members
- abstract members are allowed only in an abstract class whereas virtual members are allowed in any class
- abstract members are virtual whereas virtual members are not abstract
When applied to a class, abstract means...
- the class is meant only as a base class
- we cannot directly instantiate the abstract class
- the class may contain normal, virtual, and/or abstract members
- the class cannot be sealed (i.e. the class cannot forbid inheritance)
Sealed
- when applied to a class, sealed prevents inheritance of the entire class
- when applied to a method, property, index, or event, sealed prevents a derived class from overriding the member
- sealed members are useful to prevent further overriding of an ancestral virtual member
Illustrative Code Example
public interface IBook { object TableOfContents { get; } object Index { get; } object Cover { get; } }public interface IBreakfastRecipeBook { object MakeOmlette(object egg, object cheese, object fryingPan); object MakeToast(object bread, object toaster); object MakeTea(object teaBag, object pot, object kettle, object water); }
public abstract class EnglishBreakfastRecipeBook : IBreakfastRecipeBook, IBook { // from IBreakfastRecipeBook public abstract object MakeToast(object bread, object toaster); public abstract object MakeOmlette(object egg, object cheese, object fryingPan); public object MakeTea(object teaBag, object pot, object kettle, object water) { return new { }; }
// from IBook public object TableOfContents { get { return new { }; } } public object Index { get { return new { }; } } public object Cover { get { return new { }; } } // new member public virtual object MakeCereal(object cereal, object milk, object bowl) { return new { }; }
}
public class ExampleEnglishRecipeBook : EnglishBreakfastRecipeBook { public override object MakeToast(object bread, object toaster) { return new { }; }
public override object MakeOmlette(object egg, object cheese, object fryingPan) { return new { }; } public override sealed object MakeCereal(object cereal, object milk, object bowl) { return new { }; }
}
public class EnglishRecipeBookVersion1 : ExampleEnglishRecipeBook { public override object MakeToast(object bread, object toaster) { return new { }; }
//public override object MakeCereal(object cereal, object milk, object bowl) //{ // return new { }; //}
}