The effect of the Obsolete attribute on a class is ignored when there's an interface involved

The effect of the Obsolete attribute on a class is ignored when there's an interface involved. It caught me by surprise, but on second thought makes complete sense!

The effect of the Obsolete attribute on a class is ignored when there's an interface involved

While marking some code obsolete the other day, I noticed an unexpected behavior (at first glance).

Say you have a class, and an interface it implements. The second argument (true) indicates whether or not you’re allowed to call the method, and it means the difference between a simple warning and an error preventing compilation.

public interface ISampleClass
{
    void SomeOldMethod();
}
 
public class SampleClass : ISampleClass
{
    [Obsolete("This is the old way. No one uses it anymore. Shame on you!!", true)]
    public void SomeOldMethod()
    {
        // This is old. I should probably remove and refactor, but I'm too scared.
 
        Console.WriteLine("Oooooooooooooollllllllllld stuff.");
        Console.WriteLine();
        Console.WriteLine("Press any key to get out of here.");
 
        Console.ReadLine();
    }
}

Instantiate SampleClass directly and call SomeOldMethod(), and you’ll get the expected error:

obsolete error indicator

Program against the interface (i.e. for testing or when using WCF service contracts; also, you wouldn’t really “new” up SampleClass like this), and you won’t receive an error; in fact, the program will compile and run normally.

ISampleClass sc = new SampleClass();
 
sc.SomeOldMethod();

This seemed confusing at first – my Obsolete attribute is seemingly being ignored.

It makes more sense though, given that a single interface can be implemented by any number of classes. What should happen when the class marks a method obsolete, but the interface doesn’t? Consider the following:

public interface ISampleClass
{
    void SomeOldMethod();
}
 
public class SampleClass : ISampleClass
{
    [Obsolete("This is the old way. No one uses it anymore. Shame on you!!", true)]
    public void SomeOldMethod()
    {
        // This is old. I should remove and refactor, but I'm too scared.
    }
}
 
public class SampleClass2 : ISampleClass
{
    public void SomeOldMethod()
    {
        // Old, but not obsolete.
    }
}
 
public class SampleClass3 : ISampleClass
{
    public void SomeOldMethod()
    {
        // Why mess with perfection?
    }
}

One class marks the code obsolete; two other classes do not. Should any reference to ISampleClass.SomeOldMethod cause an error, or not? It’s ambiguous, and so any potential presence of the attribute on the classes themselves are ignored in favor of the interface.

An interface is a contract. Any class implementing a given interface is guaranteed to implement the members (methods, properties, etc) in it (those members may do nothing, or throw a NotImplementedException, but they’re there at least). Similarly, if the interface (the contract) doesn’t indicate that the method is obsolete, then it’s not. And so all of these compile and run just fine:

ISampleClass sc = new SampleClass();
sc.SomeOldMethod();
 
ISampleClass sc2 = new SampleClass2();
sc2.SomeOldMethod();
 
ISampleClass sc3 = new SampleClass3();
sc3.SomeOldMethod();

Basically, it’s not enough to mark the class itself. If you also have interfaces that the class is implementing, mark those as well. (Actually, marking the interface is enough, but then that just leads to confusion. Might as well mark both.)

public interface ISampleClass
{
    [Obsolete("This is the old way. No one uses it anymore. Shame on you!!", true)]
    void SomeOldMethod();
}
 
public class SampleClass : ISampleClass
{
    [Obsolete("This is the old way. No one uses it anymore. Shame on you!!", true)]
    public void SomeOldMethod()
    {
        // This is old. I should remove and refactor, but I'm too scared.
    }
}

More Reading