A class has one base class and may implement any number of interfaces. Inheritance members are in C# specification clause 15; interfaces are in clause 19.
Base classes
class_base:
: class_type
: interface_type_list
: class_type , interface_type_list
public class Animal
{
public virtual string Sound() => "...";
}
public class Dog : Animal, IComparable<Dog>
{
public override string Sound() => "woof";
public string Both() => base.Sound() + Sound();
public int CompareTo(Dog? other) => 0;
}
- A class with no base class named derives from
object. Every type ends atobject, which gives itToString,Equals,GetHashCode,GetTypeandMemberwiseClone. - A class inherits every member of its base except constructors and finalizers. Private members are inherited but not accessible.
base.Memberreaches the inherited member, andbase(…)calls a base constructor.- A class may not derive from itself, or from
System.Array,System.Delegate,System.EnumorSystem.ValueType.
Virtual dispatch
A virtual method can be overridden in a derived class (clause 15.6.4). A
call is resolved in two steps: the compiler picks the overload using the variable’s
compile-time type, and at run time the override is chosen from the object’s actual type.
Calls to non-virtual members use the compile-time type only.
Animal a = new Dog();
a.Sound(); // "woof": virtual, the object's type decides
| Modifier | On a member |
|---|---|
virtual | Can be overridden; has a body |
override | Replaces an inherited virtual, abstract or override member |
abstract | Has no body; must be overridden. Implicitly virtual |
sealed override | Overrides, and stops further overriding |
new | Hides an inherited member; it does not take part in dispatch |
static | Cannot be virtual, except in an interface |
- An override has the same signature and return type as the member it overrides, and the same accessibility. It cannot change a member between static and instance.
- Since C# 9 an override may return a more derived type (covariant return).
- Methods, properties, indexers and events can be virtual. Fields cannot.
- A member that hides an inherited one should say
new. Without it the code compiles with a warning. The two members coexist: which one is used depends on the variable’s type.
Abstract classes
An abstract class cannot be instantiated (clause 15.2.2). It may declare
abstract members, and each must be overridden by a non-abstract derived class.
public abstract class Shape
{
public abstract double Area { get; }
public override string ToString() => $"area {Area}";
}
public class Circle(double r) : Shape
{
public override double Area => Math.PI * r * r;
}
An abstract class may still have constructors, fields and normal members.
abstract override forces a further-derived class to give a new
implementation of a virtual member.
Sealed classes
A sealed class cannot be derived from (clause 15.2.2). It cannot be
abstract. sealed override on a member stops further overriding of that
member.
Closed classes
A closed class (C# 15) can have direct subclasses only in its own assembly
and module. Because the direct subclasses are known at compile time, a
switch expression that handles each of
them needs no default arm (feature specification Closed hierarchies).
public closed record class GateState;
public record class Closed : GateState;
public record class Open(float Percent) : GateState;
string Describe(GateState state) => state switch
{
Closed => "closed",
Open(var percent) => $"{percent}% open",
// no default arm: every direct subclass is handled
};
- A
closedclass is implicitlyabstract. It cannot be combined withsealed,staticor an explicitabstract. - The restriction covers direct subclasses only. A subclass that is not itself
closedorsealedcan be derived from elsewhere. - If a generic class derives directly from a closed class, every type parameter of the derived class must be used in its base class specification.
closedapplies to classes, including record classes.
Interfaces
An interface is a contract that classes and structs implement (clause 19). It may hold instance methods, properties, indexers and events, and, since C# 8, default implementations, static members and nested types. It has no instance fields.
public interface ILogger
{
void Write(string message); // implementing types must define this
void WriteLine(string message) => Write(message + "\n"); // default implementation, C# 8
static ILogger Null { get; } = new NullLogger(); // static member
}
- Interface members are
publicby default. Since C# 8 they may also beprivate,protected,internalorprotected internal. - An interface can inherit from several interfaces:
interface IB : IA, IDisposable. - A class implements an interface member with a public instance member of the same signature, or with an explicit implementation.
- A struct converted to an interface is boxed.
Explicit interface implementation
An explicit implementation has the interface’s name in the member’s name (clause 19.6.2). It has no access modifier, and can be called only through the interface. It resolves clashes when two interfaces have members with the same name.
public class Printer : IDocument, IPicture
{
void IDocument.Print() { }
void IPicture.Print() { }
}
((IDocument)printer).Print();
Default interface methods
Since C# 8 an interface member can have a body (clause 19.4). Classes that implement the interface inherit it only through the interface: a default method cannot be called on the class type directly. This needs runtime support, present since .NET Core 3.0; the .NET Framework does not have it.
- If several interfaces supply a default, the most specific one is used (clause 19.4.10). If none is most specific, the class must supply its own implementation, or the compiler reports an error.
- A derived interface can re-abstract an inherited member, or override it with an explicit member.
- A class may re-implement an interface (clause 19.6.7) by naming it again in its base list.
Static abstract and static virtual members
Since C# 11, interfaces may declare static abstract and
static virtual members, including operators. Each implementing type
supplies them, and generic code can call them through a type parameter. This feature is
the basis of generic maths in .NET.
public interface IAddable<T> where T : IAddable<T>
{
static abstract T Zero { get; }
static abstract T operator +(T a, T b);
}
static T Sum<T>(IEnumerable<T> values) where T : IAddable<T>
{
T total = T.Zero;
foreach (var v in values) total += v;
return total;
}
From C# 15, non-virtual static members can be declared and used in an interface even
when the runtime lacks default interface support. Instance defaults,
static abstract and static virtual members still need it.