How to Resolve Error "CS0809: Obsolete member overrides non-obsolete member" in C#
The Compiler Warning CS0809 is a design consistency warning. The message reads: "Obsolete member 'Derived.Method' overrides non-obsolete member 'Base.Method'".
This warning occurs when a derived class overrides a virtual method and marks that override with the [Obsolete] attribute, but the original method in the base class is not marked as obsolete.
This is problematic because of Polymorphism. If a caller accesses your object via a reference to the Base class, the compiler will look at the base method definition (which is not obsolete) and allow the call without any warnings. This effectively bypasses your deprecation warning, rendering the [Obsolete] attribute on the override useless in many contexts.
Understanding the Polymorphism Trap
The goal of [Obsolete] is to warn developers not to use a specific member. However, consider this flow:
- Base Class: Defines
public virtual void Save(). (Safe to use). - Derived Class: Overrides
Save()and marks it[Obsolete]. (Unsafe to use). - Usage:
BaseClass obj = new DerivedClass();
obj.Save(); // The compiler checks BaseClass.Save. It sees no attribute. No Warning.
At runtime, DerivedClass.Save() executes (because it is an override), but the developer was never warned. C# flags this setup with CS0809 to tell you that your "Do Not Use" sign is easily invisible.
Scenario: The Leaky Abstraction
This typically happens when an API evolves, and a specific implementation of a method becomes deprecated, but the interface contract remains valid.
Example of error:
using System;
public class PaymentGateway
{
// The base method is considered valid
public virtual void ProcessPayment()
{
Console.WriteLine("Processing generic payment...");
}
}
public class LegacyCheckProcessor : PaymentGateway
{
// ⛔️ Warning CS0809: Obsolete member 'LegacyCheckProcessor.ProcessPayment'
// overrides non-obsolete member 'PaymentGateway.ProcessPayment'.
[Obsolete("Checks are no longer accepted.")]
public override void ProcessPayment()
{
Console.WriteLine("Processing check...");
}
}
If a user writes PaymentGateway pg = new LegacyCheckProcessor(); pg.ProcessPayment();, they will not see the warning message "Checks are no longer accepted."
Solution 1: Mark the Base Member as Obsolete
If the method is fundamentally flawed or deprecated across the entire inheritance hierarchy, the most correct fix is to mark the base method as obsolete as well.
Solution:
public class PaymentGateway
{
// ✅ Correct: Mark the base as obsolete too.
// Now, anyone calling .ProcessPayment() on ANY PaymentGateway will get a warning.
[Obsolete("This payment method structure is deprecated.")]
public virtual void ProcessPayment()
{
Console.WriteLine("Processing generic payment...");
}
}
public class LegacyCheckProcessor : PaymentGateway
{
[Obsolete("Checks are no longer accepted.")]
public override void ProcessPayment()
{
Console.WriteLine("Processing check...");
}
}
This solution is often impossible if you do not own the Base Class (e.g., if you are overriding Object.ToString() or a method from a third-party library).
Solution 2: Remove the Attribute (or Redesign)
If the base contract forces you to implement the method (e.g., it is abstract), you technically cannot "deprecate" just your implementation while still fulfilling the contract.
If the method is dangerous to call on this specific class, consider throwing a runtime exception instead, or mark the entire class as obsolete.
Option A: Deprecate the Class
// ✅ Correct: The entire class is bad, so don't use the class at all.
[Obsolete("Use ModernCheckProcessor instead.")]
public class LegacyCheckProcessor : PaymentGateway
{
// Remove [Obsolete] from the method itself
public override void ProcessPayment()
{
Console.WriteLine("Processing check...");
}
}
Option B: Runtime Exception
public class LegacyCheckProcessor : PaymentGateway
{
// ✅ Correct: Remove the attribute. Throw an exception if used.
public override void ProcessPayment()
{
throw new NotSupportedException("Checks are strictly forbidden.");
}
}
Solution 3: Suppress the Warning (Pragmatic Approach)
Sometimes you do want to warn users who are accessing LegacyCheckProcessor directly, even if you acknowledge that users casting to PaymentGateway won't see the warning. In this specific case, you can suppress CS0809 to tell the compiler: "I understand the polymorphism loophole, but keep the attribute anyway."
Solution:
public class LegacyCheckProcessor : PaymentGateway
{
// ✅ Correct: Warning suppressed.
// Users writing 'var x = new LegacyCheckProcessor(); x.ProcessPayment();' WILL get a warning.
// Users writing 'PaymentGateway x = ...; x.ProcessPayment();' will NOT get a warning.
#pragma warning disable CS0809
[Obsolete("Checks are no longer accepted.")]
public override void ProcessPayment()
{
Console.WriteLine("Processing check...");
}
#pragma warning restore CS0809
}
Conclusion
CS0809 alerts you that your deprecation strategy has a loophole.
- Ideal Fix: Mark the base method
[Obsolete]if the entire functionality is being retired. - Architecture Fix: Mark the entire derived
classas[Obsolete]if that specific implementation is no longer useful. - Pragmatic Fix: Use
#pragma warning disable CS0809if you want to warn direct consumers of the derived class while accepting that polymorphic calls will bypass the warning.