TL;DR:

  • Method overriding in C# lets you customize or replace method behavior in child classes without changing the parent class.
  • Mark the parent method as virtual or abstract, and use the override keyword in the child.
  • This enables runtime polymorphism—C# automatically calls the correct method based on the object’s actual type, not just its reference.
  • Use it to extend frameworks, base classes, or shared APIs without rewriting existing logic.

In this guide, you’ll learn how to use method overriding in C#, when to use override vs new keywords, and how polymorphism works at runtime. Method overriding in C# is how a child class can change or extend what a parent class method does. When implementing method overriding in C#, you’ll need this whenever different classes need to handle the same action in their own way. In real-world applications, this means you can keep your interfaces consistent while letting each class implement things differently.

What is Method Overriding? C# Virtual Method Fundamentals

Method overriding is simply when a child class rewrites a method from its parent class. To override a method in C#, you need to keep the same method name, return type, and parameters, just change what the method actually does. The C# override keyword usage is essential for the compiler to understand your intent to replace the parent implementation.


classDiagram
    class BaseClass {
        +virtual void DoSomething()
    }
    
    class DerivedClassA {
        +override void DoSomething()
    }
    
    class DerivedClassB {
        +override void DoSomething()
    }
    
    BaseClass <|-- DerivedClassA
    BaseClass <|-- DerivedClassB
    
    note for BaseClass "Declares virtual method<br/>Defines base implementation"
    note for DerivedClassA "Overrides with specific implementation"
    note for DerivedClassB "Overrides with different implementation"

    

How Method Overriding Works in C#

What does this give you? With method overriding, you can:

  1. Create polymorphic behavior (same method name, different behaviors)
  2. Customize how inherited methods work without touching the parent code
  3. Follow the Open/Closed Principle, your code is open for extension but closed for modification
  4. Implement C# polymorphism override patterns where behavior changes based on the actual object type

Why Method Overriding Matters

Why should you care about method overriding? Here’s why it matters when building clean, maintainable code:

1. Makes Runtime Polymorphism Possible

With polymorphism, you can write code that says “call this method” without caring which specific class actually runs it. When you use a C# override method approach, the right version gets called based on the actual object type. This is different from method overloading where the selection happens at compile time—C# method override vs overload has this crucial distinction. With proper method overriding, you can add new types later without having to change your existing code.


sequenceDiagram
    participant Client as Client Code
    participant BR as BaseReference : BaseClass
    participant RT as Runtime
    participant BO as BaseObject
    participant DO as DerivedObject
    
    Client->>BR: Call virtual Method()
    BR->>RT: Which implementation to use?
    RT-->>BR: Check actual object type
    
    alt If reference points to BaseObject
        BR->>BO: Execute BaseObject.Method()
        BO-->>Client: Return base implementation result
    else If reference points to DerivedObject
        BR->>DO: Execute DerivedObject.Method()
        DO-->>Client: Return derived implementation result
    end
    
    Note over BR,RT: The same method call behaves differently<br/>based on the actual runtime type

    

Runtime Polymorphism with Method Overriding

2. Creates Clear Extension Points: How to Override Base Class Methods in C#

Good frameworks and libraries mark methods as virtual when they expect you might want to customize them. These C# virtual method declarations give you official “hooks” to change behavior without messing with the original source code. Understanding how to override base class methods in C# lets you effectively use these extension points.

3. Follows the Open/Closed Principle

The “O” in SOLID principles stands for Open/Closed: code should be open for extension but closed for modification. Method overriding is exactly this, you extend functionality without changing the original code.

4. Cuts Down on Copy-Paste Code

Instead of copying a big chunk of code just to change one small part, you can inherit everything and only override the bit that needs to be different. Less duplicated code means fewer bugs and easier maintenance.

5. Keeps Your Code Clean and Organized

With method overriding, you can clearly separate what needs to happen (defined in the base class) from how it happens (implemented in child classes). The parent lays out the structure, and each child fills in the details in its own way.

C# Method Overriding Examples: Real-World Scenarios

Let’s look at some practical C# method overriding examples that demonstrate these concepts in action.

Payment Processing System

Consider an e-commerce application that needs to handle multiple payment methods:

// Base payment processor
public abstract class PaymentProcessor
{
    protected decimal _transactionFee;
    
    public virtual decimal CalculateTransactionFee(decimal amount)
    {
        return _transactionFee;
    }
    
    public virtual async Task<PaymentResult> ProcessPaymentAsync(
        PaymentDetails details, decimal amount, CancellationToken cancellationToken)
    {
        // Common validation logic
        if (amount <= 0)
            return new PaymentResult { Success = false, ErrorCode = "INVALID_AMOUNT" };
            
        // Log the payment attempt - implementation removed for brevity
        
        // This will be overridden by specific processors
        return await ExecutePaymentAsync(details, amount, cancellationToken);
    }
    
    // Method to be overridden by derived classes
    protected abstract Task<PaymentResult> ExecutePaymentAsync(
        PaymentDetails details, decimal amount, CancellationToken cancellationToken);
}

// Credit card payment implementation
public class CreditCardProcessor : PaymentProcessor
{
    public CreditCardProcessor()
    {
        _transactionFee = 0.025m; // 2.5%
    }
    
    public override decimal CalculateTransactionFee(decimal amount)
    {
        // Credit cards have a minimum fee
        decimal standardFee = base.CalculateTransactionFee(amount);
        return Math.Max(standardFee * amount, 0.50m);
    }
    
    protected override async Task<PaymentResult> ExecutePaymentAsync(
        PaymentDetails details, decimal amount, CancellationToken cancellationToken)
    {
        var cardDetails = details as CreditCardDetails 
            ?? throw new ArgumentException("Invalid payment details");
            
        // Credit card-specific implementation - details removed for brevity
        // Connect to credit card gateway, process payment, handle response
        
        return new PaymentResult { Success = true };
    }
}

// PayPal implementation
public class PayPalProcessor : PaymentProcessor
{
    public PayPalProcessor()
    {
        _transactionFee = 0.029m; // 2.9%
    }
    
    public override decimal CalculateTransactionFee(decimal amount)
    {
        // PayPal charges percentage + fixed fee
        return (base.CalculateTransactionFee(amount) * amount) + 0.30m;
    }
    
    protected override async Task<PaymentResult> ExecutePaymentAsync(
        PaymentDetails details, decimal amount, CancellationToken cancellationToken)
    {
        var paypalDetails = details as PayPalDetails 
            ?? throw new ArgumentException("Invalid payment details");
            
        // PayPal-specific implementation - details removed for brevity
        
        return new PaymentResult { Success = true };
    }
}

How Method Overriding Is Used in Production

In the above example, a developer might use these payment processors like this:

public class OrderService
{
    private readonly Dictionary<PaymentMethod, PaymentProcessor> _processors;
    
    public OrderService(IEnumerable<PaymentProcessor> availableProcessors)
    {
        // Initialize processors - implementation removed for brevity
    }
    
    public async Task<OrderResult> ProcessOrderAsync(
        Order order, PaymentDetails paymentDetails, CancellationToken cancellationToken)
    {
        // Get the appropriate payment processor based on the customer's choice
        if (!_processors.TryGetValue(order.PaymentMethod, out var processor))
        {
            return new OrderResult { Success = false, Error = "Payment method not supported" };
        }
        
        // Process payment - the correct implementation runs based on the processor type
        // This is where polymorphism happens - the right ExecutePaymentAsync runs
        var paymentResult = await processor.ProcessPaymentAsync(
            paymentDetails, order.TotalAmount, cancellationToken);
            
        // Result handling - removed for brevity
        
        return new OrderResult { Success = true };
    }
}

Notice how the runtime type of the object determines which implementation gets executed.

C# Method Overriding Rules

For successful C# inheritance override method implementations, follow these essential rules:

  • Mark parent methods with virtual, abstract, or override - Without this, C# won’t let you override them
  • Add the override keyword in child methods - This tells the compiler “Yes, I’m intentionally overriding this”
  • Keep the signature exactly the same - Same method name, return type, and parameters (no changes allowed)
  • Don’t reduce accessibility - Your override can be more accessible than the parent method, but never less
  • Understand C# override vs new differences - The new keyword hides rather than overrides, breaking the polymorphic behavior

flowchart TD
    Start([Start]) --> BaseVirtual{Base method<br>virtual/abstract?}
    
    BaseVirtual -->|No| CantOverride[Cannot be overridden]
    BaseVirtual -->|Yes| ChildOverride{Child has<br>override keyword?}
    
    ChildOverride -->|No| MethodHiding[Method hiding<br>with 'new' keyword]
    ChildOverride -->|Yes| SignatureMatch{Signatures<br>match?}
    
    SignatureMatch -->|No| CompileError[Compile Error]
    SignatureMatch -->|Yes| AccessLevel{Child method<br>accessibility}
    
    AccessLevel -->|Less than base| CompileError
    AccessLevel -->|Equal or greater| SuccessfulOverride[Successful Override]
    
    SuccessfulOverride --> RuntimeCall[Runtime: Call appropriate<br>implementation based<br>on actual object type]
    MethodHiding --> BaseMethodCalled[Runtime: Base or child method<br>called based on reference type]
    
    style CompileError fill:#f55,stroke:#333,stroke-width:2px
    style SuccessfulOverride fill:#5d5,stroke:#333,stroke-width:2px
    style MethodHiding fill:#fa5,stroke:#333,stroke-width:2px
    style BaseMethodCalled fill:#fa5,stroke:#333,stroke-width:2px
    style RuntimeCall fill:#5d5,stroke:#333,stroke-width:2px

    

Method Overriding Resolution Process in C#

C# Inheritance Override Method: Notification System Example

Most applications need to send notifications in different ways. Method overriding works great for this:

public abstract class NotificationService
{
    protected readonly ILogger _logger;
    protected readonly NotificationSettings _settings;

    protected NotificationService(ILogger logger, NotificationSettings settings)
    {
        _logger = logger;
        _settings = settings;
    }

    // Template method pattern with parts that can be overridden
    public async Task<NotificationResult> SendNotificationAsync(
        NotificationRequest request, CancellationToken cancellationToken)
    {
        try
        {
            // Common validation - removed for brevity
            
            // Format the notification (overridden by subclasses)
            var formattedContent = FormatNotificationContent(request);

            // Send notification (implementation varies by channel)
            bool success = await SendAsync(formattedContent, request, cancellationToken);

            return new NotificationResult(success);
        }
        catch (Exception ex)
        {
            return new NotificationResult(false, ex.Message);
        }
    }

    // These methods are overridden by specific notification channels
    protected abstract Task<bool> SendAsync(
        string formattedContent, 
        NotificationRequest request, 
        CancellationToken cancellationToken);

    protected virtual string FormatNotificationContent(NotificationRequest request)
    {
        return request.Message;
    }
}

// Email notification implementation
public class EmailNotificationService : NotificationService
{
    private readonly IEmailClient _emailClient;

    public EmailNotificationService(
        ILogger<EmailNotificationService> logger,
        NotificationSettings settings,
        IEmailClient emailClient) : base(logger, settings)
    {
        _emailClient = emailClient;
    }

    protected override string FormatNotificationContent(NotificationRequest request)
    {
        // Add email-specific formatting like HTML
        var baseContent = base.FormatNotificationContent(request);
        return $"<html><body>{baseContent}</body></html>";
    }

    protected override async Task<bool> SendAsync(
        string formattedContent, 
        NotificationRequest request, 
        CancellationToken cancellationToken)
    {
        // Email-specific implementation - details removed for brevity
        return await _emailClient.SendEmailAsync(new EmailRequest(), cancellationToken);
    }
}

// Push notification implementation
public class PushNotificationService : NotificationService
{
    private readonly IPushNotificationProvider _pushProvider;

    public PushNotificationService(
        ILogger<PushNotificationService> logger,
        NotificationSettings settings,
        IPushNotificationProvider pushProvider) : base(logger, settings)
    {
        _pushProvider = pushProvider;
    }

    protected override string FormatNotificationContent(NotificationRequest request)
    {
        // Push notifications need to be shorter
        var content = base.FormatNotificationContent(request);
        return content.Length <= 100 ? content : content.Substring(0, 97) + "...";
    }

    protected override async Task<bool> SendAsync(
        string formattedContent, 
        NotificationRequest request, 
        CancellationToken cancellationToken)
    {
        // Push notification-specific implementation - details removed for brevity
        return await _pushProvider.SendPushNotificationAsync(new PushRequest(), cancellationToken);
    }
}

C# Override vs Overload Difference: Choosing the Right Pattern

Method overriding isn’t the only way to customize behavior. Understanding the C# override vs overload difference is just the start—there are other patterns to consider as well. Here’s how method overriding compares to other common patterns:


flowchart TB
    Start([Need to customize behavior?]) --> NeedInheritance{Is inheritance<br>appropriate?}
    
    NeedInheritance -->|Yes| NeedStability{Is base class<br>hierarchy stable?}
    NeedInheritance -->|No| UseStrategy[Use Strategy Pattern<br>Composition over Inheritance]
    
    NeedStability -->|Yes| UseOverride[Use Method Overriding<br>virtual/override keywords]
    NeedStability -->|No| NeedAddBehavior{Need to add<br>behavior dynamically?}
    
    NeedAddBehavior -->|Yes| UseDecorator[Use Decorator Pattern<br>Wrap objects with new behavior]
    NeedAddBehavior -->|No| UseStrategy
    
    classDef decision fill:#999,stroke:#333,stroke-width:2px
    classDef solution fill:#bbf,stroke:#333,stroke-width:2px
    
    class NeedInheritance,NeedStability,NeedAddBehavior decision
    class UseOverride,UseStrategy,UseDecorator solution

    

Comparing Method Overriding with Alternative Patterns

PatternWhen to UseProsCons
Method OverridingWhen you have a stable base class hierarchy and need to customize behavior in derived classesSimple, clean, built into the languageCreates tight coupling, requires inheritance
Strategy PatternWhen you need to switch algorithms at runtime or avoid inheritanceMore flexible, no inheritance requiredMore complex, more objects
Decorator PatternWhen you need to add responsibilities without subclassingFlexible composition, adheres to SRPCan lead to many small classes
Method OverloadingWhen the same operation needs different parameter setsClear API, compile-time resolutionNo runtime polymorphism like C# method override

When Should You Use C# Override Sealed Method or Abstract Methods?

Method overriding is your best choice when:

  1. You have a clear parent-child relationship between your classes
  2. Your base class already does most of the work right, with just parts needing customization
  3. You need different classes to handle the same method in their own way
  4. You want to ensure all classes follow the same pattern or protocol
  5. You need to decide which implementation to run based on the object’s type at runtime

Remember that you cannot use C# override sealed methods as they’re specifically designed to prevent further specialization. Also, C# abstract class method override patterns are particularly useful when you want to define a structure that requires implementation by derived classes.

Method Overriding Best Practices in C#: C# Base Method Call in Override

  • Respect the Contract: Don’t change what the method is supposed to do, just how it does it
  • Call Base When Appropriate: Use base method call in override (base.Method()) when you want to add to the parent behavior, not replace it entirely
  • Use Protected Virtual Methods: Create “hook points” in your base class that child classes can customize
  • Document Your Intent: Comment on why a method is virtual and how others should override it
  • Add Sealed When Needed: Use the sealed keyword to say “you can override my methods, but not this specific one”
  • Watch Your Dependencies: Make sure your override doesn’t accidentally break something the base class needs
  • Match Signatures Precisely: When you C# override method implementations, ensure parameter types and order match exactly

The Bottom Line

Method overriding is essential to object-oriented programming and it’s what makes polymorphism possible. The C# inheritance override method pattern creates a powerful mechanism for specialization. With overriding, your parent class sets the rules, and child classes fill in the details.

Look at any well-built application (like the payment and notification examples above), and you’ll see method overriding at work. The beauty of it? You can add new types tomorrow without touching today’s code. It’s the perfect balance between consistency and flexibility.

Whether you’re working with C# virtual methods in a framework or implementing your own abstract class hierarchies, proper method overriding is a core skill for any C# developer building maintainable, flexible systems.

Frequently Asked Questions

What is method overriding in C#?

Method overriding in C# allows a derived class to provide a specific implementation of a method already defined in its base class. The base method must be marked as virtual, and the derived method must use the override keyword. This enables polymorphic behavior.

How do you override a method in C#?

To override a method, declare the base class method as virtual and the derived class method as override with the same signature. Example:

public class Animal { public virtual void Speak() { } }
public class Dog : Animal { public override void Speak() { Console.WriteLine("Woof"); } }

What is the difference between override and new keywords in C#?

The override keyword replaces a virtual method from the base class, enabling polymorphism. The new keyword hides the base method, but does not provide polymorphic behavior. Use override for true method overriding. see here.

Why is method overriding important in object-oriented programming?

Method overriding enables polymorphism, allowing different classes to respond differently to the same method call. This makes code more flexible and maintainable, supporting real-world scenarios like different animal sounds or payment processing.

Can you override a non-virtual method in C#?

No, only methods marked as virtual, abstract, or override in the base class can be overridden. Attempting to override a non-virtual method will result in a compile-time error.

What happens if you do not use the override keyword in the derived class?

If you do not use override but declare a method with the same signature, the new method hides the base method instead of overriding it. This can lead to unexpected behavior when using base class references.

How does polymorphism work with method overriding in C#?

Polymorphism allows you to use a base class reference to call overridden methods in derived classes. The actual method executed is determined at runtime based on the object’s type.

Animal a = new Dog(); a.Speak(); // Calls Dog's Speak

Can constructors be overridden in C#?

No, constructors cannot be overridden in C#. Only methods, properties, indexers, and events marked as virtual can be overridden.

What is the purpose of the base keyword in method overriding?

The base keyword allows a derived class to call the implementation of a method from its base class. This is useful when you want to extend, not completely replace, the base behavior.

public override void Speak() { base.Speak(); Console.WriteLine("Extra"); }

When should you use method overriding in C#?

Use method overriding when you want derived classes to provide specialized behavior for a method defined in a base class. It is essential for implementing polymorphic behavior and following OOP best practices.

Related Posts