Welcome to Shaun Luttin's public notebook. It contains rough, practical notes. The guiding idea is that, despite what marketing tells us, there are no experts at anything. Sharing our half-baked ideas helps everyone. We're all just muddling thru. Find out more about our work at bigfont.ca.

Inheritance, Interfaces, and the Abstract, Virtual, and Sealed Keywords in C#

Tags: C#, keywords

Introduction

This is meant to be an incomplete overview of inheritance related keywords in C#. There are more details in the MSDN documentation.

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 { };
//}

}