Back to Blog
C#

Using the C# Base Keyword Correctly

Learn how the c# base keyword calls base class constructors, methods, and properties, with practical examples and common pitfalls.

C# inheritancebase keywordconstructor chainingmethod overridingobject-oriented programming
Illustration of the C# base keyword showing a derived class calling its base class method.

c# base keyword requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The C# base keyword serves one clear purpose: it gives a derived class a way to access members of its base class that would otherwise be hidden or shadowed. You use it inside an instance method, property accessor, or constructor of a derived class. It cannot be used from static methods, and it cannot reach further up the inheritance chain than the immediate base class.

The Core Syntax of Base

base behaves like an implicit reference to the base class instance. In an instance member of a derived class, base resolves to the base class's implementation of a member.

public class Animal { public virtual void Speak() { Console.WriteLine("Animal sound"); } } public class Dog : Animal { public override void Speak() { Console.WriteLine("Dog barks"); base.Speak(); // Calls Animal.Speak() } }

In this example, Dog.Speak overrides Animal.Speak. Calling base.Speak() inside Dog.Speak invokes the base implementation directly, allowing the derived class to extend or decorate the base behavior without duplicating it.

base is not a variable you can pass around. It is a contextual keyword that only makes sense inside a derived class instance member. You cannot use base in a static method because there is no instance to resolve.

Calling Base Class Constructors

When a derived class constructor runs, the base constructor must also run. If you do not explicitly call a base constructor, the compiler will call the parameterless base constructor implicitly. If the base class has no parameterless constructor, you must call a specific one.

public class Vehicle { protected string Engine; public Vehicle(string engine) { Engine = engine; } } public class Car : Vehicle { public Car(string engine) : base(engine) { } }

The : base(engine) syntax ensures that the Vehicle constructor initializes its Engine field before the rest of Car’s constructor body executes. This ordering matters: base initialization runs first, so any work the derived class does later can rely on the base state being ready.

If you omit the base call when no parameterless constructor exists, the code will not compile. Always check whether the base class exposes an appropriate constructor for your derived class's needs.

Overriding Virtual Members with Base.Method

A common use case for base is calling the original implementation of an overridden method. You might do this to add logging, validation, or additional behavior around the base logic.

public class Repository { public virtual void Save(string data) { Console.WriteLine($"Saving: {data}"); } } public class AuditedRepository : Repository { public override void Save(string data) { Console.WriteLine($"Audit log: {DateTime.UtcNow}"); base.Save(data); } }

Here, AuditedRepository.Save writes an audit entry before delegating to Repository.Save. The base implementation still handles the actual persistence, while the derived class adds cross-cutting behavior.

Important limitation: base can only call the immediate base class implementation. If you have a chain like A <- B <- C, then inside C you can call base.Method() which invokes B.Method(), but you cannot directly call A.Method() from C. To execute A.Method(), you would need B.Method() to call it and C to call B.Method().

When to Use Base for Properties

You can also use base to access a base class property, typically when the derived class hides a property with the new keyword or when you need the original getter or setter logic.

public class Person { public string Name { get; set; } } public class Employee : Person { public new string Name { get { return $"Employee: {base.Name}"; } set { base.Name = value?.Trim(); } } }

Setting base.Name lets you apply normalization before storing the value. Reading base.Name retrieves the original value without the derived formatting.

Base and Constructor Chaining

Chaining constructors with base helps avoid duplicated initialization logic across derived classes. Consider a base class with several optional parameters.

public class Report { protected string Title; protected string Description; public Report(string title) : this(title, string.Empty) { } public Report(string title, string description) { Title = title; Description = description; } } public class DetailedReport : Report { public DetailedReport(string title) : base(title) { } public DetailedReport(string title, string description) : base(title, description) { } }

The derived class exposes multiple constructors, each mapping to the appropriate base constructor. This keeps the base class in control of its invariants while giving derived classes convenient creation paths.

Avoiding Common Mistakes with Base

A frequent mistake is calling base when you actually want to call the base method from anywhere other than a derived instance method. For example, you cannot call base.Method() from a static method or from code that is not part of a derived class.

Another mistake is assuming base reaches up multiple levels. Consider this:

public class GrandParent { public virtual void Foo() => Console.WriteLine("GrandParent.Foo"); } public class Parent : GrandParent { public override void Foo() => Console.WriteLine("Parent.Foo"); } public class Child : Parent { public override void Foo() { base.Foo(); // Calls Parent.Foo, not GrandParent.Foo } }

Even though Parent.Foo calls base.Foo() internally (which invokes GrandParent.Foo), Child can only invoke Parent.Foo via base. You cannot directly call GrandParent.Foo from Child.

Finally, remember that base is not a reference to the base type’s static state. For static members, your derived class inherits access to them directly, but there is no base keyword available in static contexts.

Runtime Cost and Maintainability

Using base has no runtime overhead beyond a regular virtual method dispatch. The compiler resolves base calls to the specific base class method at compile time, so it does not introduce dynamic binding costs. The practical cost is maintainability: overusing base can create fragile coupling to base behavior.

When you call base inside an override, you create a dependency on the base implementation's exact behavior. If the base class changes its internal logic, your derived class may break or behave differently. For example, if Repository.Save starts applying its own auditing, the AuditedRepository might double-log. This coupling is acceptable when you intentionally extend base behavior, but it becomes a liability when you call base purely out of habit.

A good rule: call base only when the base implementation is part of the contract you intend to reuse. If you just need to invoke the same logic without any real extension, consider whether the method should be virtual at all.

Production Considerations for Base Calls

In production systems, base calls can interact with constructor side effects, dependency injection, and exception handling. If the base constructor performs heavy operations, every derived instance pays that cost. Be aware that base constructor calls cannot be conditional; they run before any code in the derived constructor body.

Also, base method calls can complicate exception stack traces. If base.Save throws, the call stack will include both RepoSave and AuditedRepository.Save. That is usually acceptable, but if you wrap the base call in a try-catch, ensure you rethrow properly to avoid swallowing the original failure.

When design a base class for outside consumption, document which members are safe to call via base. For example, a derived class author needs to know whether base.Save performs its own transaction handling. Without that knowledge, they might wrap it in an outer transaction, creating distributed transaction issues.

Choosing Between Base and Abstraction

Sometimes using base is the simplest option, but it is not always the best design. If you find yourself calling base in every override, the base class may be doing too much work. Consider splitting behavior into smaller virtual methods or using composition instead of inheritance.

For instance, instead of a Repository base class that fully implements Save, you might extract an IPersister interface. The derived class can then delegate to a separate persister rather than calling base.Save. This reduces coupling and makes tests easier because you can mock the persister.

That said, the base keyword remains the right tool when you need to invoke the immediate base implementation of a virtual member. It is also the only way to call a specific base constructor. Weigh the maintainability cost of tight base coupling against the simplicity of direct delegation.

Final Technical Note: Base and the new Modifier

If a derived class hides an inherited member using the new keyword rather than override, calling base still works, but it behaves differently depending on how the member is accessed.

public class Base { public void Info() => Console.WriteLine("Base.Info"); } public class Derived : Base { public new void Info() => Console.WriteLine("Derived.Info"); public void CallBase() => base.Info(); }

Inside CallBase, base.Info() invokes Base.Info even though Derived.Info hides it. However, if Derived is referenced through a Base variable, derivedAsBase.Info() would still call Derived.Info because hiding is not virtual dispatch—it is a compile-time decision based on the reference type. This behavior can be confusing, so prefer override when you intend polymorphic behavior.

Understanding how base resolves for hidden, overridden, and shadowed members prevents subtle bugs that surface only when inheritance hierarchies grow. Keep your base calls explicit and intentional, and document why each call is necessary.

c# base keyword: Practical Usage and Code Examples | RYUSLOG DEV