Using partial classes to implement hierarchical interfaces for readability

Viewed 661

I understand we can use partial classes to separate the implementations of interfaces so it is more organized and more readable.

However, what if the interface I am implementing inherits other interfaces such as below:

public interface IA
{
    void DoA();
}
public interface IB : IA
{
    void DoB();
}
public partial class B : IB
{
    public void DoB() { }
}
public partial class B : IA
{
    public void DoA() { }
}

That would be the same as doing this:

public class B : IB, IA { // ... }

The compiler allows that but I am not sure if that will get me in trouble. Can someone please clarify if it will cause issues.

I am thinking of doing this instead and use a comment but I rather go with the above approach if it will cause no issues:

public partial class B : IB
{
    public void DoB() { }
}
public partial class B // IA Implementation
{
    public void DoA() { }
}

Edit

If I do below compiler will complain that "'IA' is already listed in interface list.":

public class B : IA, IA
{
}

but it will allow this:

public class B : IB, IA { // ... }

Why would it allow it if IB inherits IA. Clearly the 2 scenarios are different since the compiler allows one and not the other. There must be a reason.

3 Answers

The compiler allows that but I am not sure if that will get me in trouble. Can someone please clarify if it will cause issues.

Most of the time this is a reasonable practice. I often use partial classes to separate out an interface implementation, or the implementation of a nested class, and you should feel confident doing so.

However, there is one situation where you can be surprised by the practice you describe of explicitly calling out an implemented interface, and that is the interface re-implementation rule.

It is best described with a little example. Suppose you have

interface I { void Foo(); }
// B does not implement I.
class B { public void Foo() {} }
class C : B, I { }

That's perfectly legal. C implements I, which means C must have method Foo, and C does have method Foo; it inherited it from B.

Now consider:

// D implements I because D derives from C
class D : C { public new void Foo() {} }
// The I is unnecessary here, right? Or is it?
class E : D, I { }

The interface re-implementation rule is: because E explicitly says that it implements I, the compiler re-does the analysis to determine which method Foo matches I.Foo. Summing up:

  • ((I)(new C())).Foo() calls B.Foo
  • ((I)(new D())).Foo() calls B.Foo
  • ((I)(new E())).Foo() calls D.Foo

This can be surprising, and it can happen unexpectedly when you unnecessarily state an interface. It almost never happens, and it almost never matters when it does, but you asked for any possible issue, and this is such a situation.

Now let's consider your follow-up question:

Clearly the two scenarios are different since the compiler allows one and not the other. There must be a reason.

As we've already seen, C#'s designers wanted a way to express "I've introduced a new member that I want to be bound to the interface members", and the somewhat obscure choice they made was to allow this redundant declaration to mean "do the re-binding now".

However, there is another design reason to allow that sort of redundancy, and moreover, to not warn about it when it happens.

A good practice to get into when thinking about odd C# design decisions is to remember that the designers of C# are explicitly worried at all times about stuff changing in versioned software. In particular, they worry about many different forms of "brittle base class failure". That is the failure mode where someone makes a change that is perfectly reasonable to a base class, which then causes a derived class to surprisingly break or behave strangely.

Consider for example this sequence of events. First, developer X writes:

interface IA { void Foo(); }

and then developer Y writes

class C { public void Foo() {} }

and then developer Z writes:

class D : C, IA { }

and then developer Y realizes, oh, I implemented the contract of IA, I can get that for free, and changes class C to

class C : IA { }

So now the question is: should the compiler give a warning on class D for being redundant? And the answer is no, the author of class D did nothing wrong, and should not be made to do work to fix a problem that doesn't actually exist.

That's why C# tends to be pretty tolerant of redundancy, though unfortunately it is not always consistently tolerant. (Re-statements of generic constraints, for example, are illegal but in my opinion should be allowed.)

In short, any time you think "C# could be telling me about this redundancy" ask yourself what if it was caused by someone else making a change that I don't control? Would that be annoying or helpful? If annoying, the language design probably suppresses the warning deliberately.

There are no issues. Partial classes are for your convenience. They don’t affect the generated code. If you can write a non-partial class, you can then split it into an arbitrary number of partial classes with no change in behavior.

The compiler doesn’t let you list the exact same interface twice in the list of implemented interfaces because that would be silly and usually would imply that you made a mistake (meant one thing but wrote another). But this behavior is only to help you not make mistakes, and thus the compiler only verifies that the interfaces don’t literally have same names in the list. If that check passes, the compiler then treats the implemented interfaces as a set of unique elements, no matter how many times an interface is added via different inheritance paths.

Suppose you have IA extending IDisposable and IB extending IDisposable, and your class implements both IA and IB. The compiler sees your class as implementing IA, IB and IDisposable as if they were themselves not extending any other interfaces.

Do note that C# is not C++, and the latter don’t have interfaces. In C++ code, the equivalent of C#’s class inheritance and interface implementation would be approximated with virtual inheritance. But modern Pascal, as well as CoDeSys-3 extensions of IEC-61131-3 provide interfaces with semantics similar to those in C#.

Hans’s admonition to stop calling interface implementation “inheritance” is to be ignored at your own peril. One is inheriting nothing when implementing an interface. The interface has no data, no implementation, it’s just a declaration of what methods and properties the implementing classes need to provide. The interfaces are essentially contracts between the user of the class and the author of the class. Inheritance is more than that. Inheritance is concrete, it means that you take a class type and add more stuff to it. Interface implementation is nothing like that: you don’t have any stuff, you just declare that here’s a list of what methods and properties are implemented, and such lists are like contracts: a method taking an “interface” type as input is not really asking for the interface. It’s asking for some concrete object that honors the interface’s contract.

I thought more about this and came to the conclusion, with confidence, that it will not cause any issues. For example, for brevity if we were to remove the concept of partial for a bit so we can concentrate on the issue at hand, if we did the following:

public class B : IB, IA
{
    public void DoA() { }
    public void DoB() { }
}

The C# compiler will not complain because even though we are unnecessarily indicating that B implements IA, it is still true. We are repeating ourselves but it is still true. Perhaps the compiler should still warn us that 'IA' is already listed in interface list or some other warning, but it does not.

Thus repeating the fact that the class implements the interface again, changes nothing and will cause no issues.

Having said that, we have to be careful if we do this in a derived class.


The compiler will not allow below and complain it hides an inherited member and if hiding is meant, then to use the new keyword.

public class C : B, IA
{
    // Compiler will not allow it without new keyword
    void DoA() { Console.WriteLine("DoA in C."); }
}

However, and this is the part we have to be careful about, if we explicitly implement the interface such as below:

public class C : B, IA
{
    void IA.DoA() { Console.WriteLine("DoA in C."); }
}

It will allow it and now we have overridden the functionality specified in B.

Here is the complete code to demonstrate what I am talking about.

public interface IA
{
    void DoA();
}
public interface IB : IA
{
    void DoB();
}
public class B : IB, IA
{
    public void DoA() { Console.WriteLine("DoA in B."); }
    public void DoB() { Console.WriteLine("DoB in B."); }
}

public class C : B, IA
{
    void IA.DoA() { Console.WriteLine("DoA in C."); }
}

And a quick test:

var b = new B();
b.DoA();
b.DoB();

var c = new C();
c.DoA();           // Prints: DoA in B.
(c as IA).DoA();   // Prints: DoA in C.

Fiddle Me

Related