PsMaster
PsMaster

Reputation: 141

C# interface implementation with derived interface

In the following sample class "SomeClass" does not implement "ISomeInterface". Why can't I implement this by passing a more derived interface which does implement the base requirement. Whatever instance would be passed it would still implement the base, am I missing something?

namespace Test
{
    public interface IBaseInterface
    {
        void DoBaseStuff();
    }

    public interface IChildInterface : IBaseInterface
    {
        void DoChildStuff();
    }

    public interface ISomeInterface
    {
        void DoSomething(IBaseInterface baseInterface);
    }

    public class SomeClass : ISomeInterface
    {
        public void DoSomething(IChildInterface baseInterface)
        {
        }
    }
}

Upvotes: 2

Views: 2720

Answers (5)

Dax Fohl
Dax Fohl

Reputation: 10781

If you could do that, then you could do this:

IAnimal cat = new Cat();
IAnimalTrainer dogTrainer = new DogTrainer();
dogTrainer.Train(cat);

An IAnimalTrainer can train any IAnimal. But a DogTrainer can only train Dogs. Thus it's illegal for DogTrainer to implement the IAnimalTrainer interface.

Upvotes: 0

Stefan Laity
Stefan Laity

Reputation: 130

You should change some interface to use some type which implements IBaseInterface, then change the method signatures to use whichever child your SomeClass wants.

public interface ISomeInterface<TSomeChild> where TSomeChild : IBaseInterface
{
    void DoSomething(TSomeChild baseInterface);
}

public class SomeClass : ISomeInterface<IChildInterface>
{
    public void DoSomething(IChildInterface baseInterface)
    {
    }
}

Upvotes: 0

Der Kommissar
Der Kommissar

Reputation: 5953

This restriction exists because the ISomeInterface expects that any IBaseInterface will satisfy the contract. That is, if you have the following:

public interface IBase {}
public interface IChildA : IBase {}
public interface IChildB : IBase {}

And an interface that expects IBase:

public interface IFoo { void Bar(IBase val); }

Then restricting this in a derived class as you would like:

public class Foo : IFoo { public void Bar(IChildA val) {} }

Would create the following problem:

IChildB something = new ChildB();
IFoo something = new Foo();
something.Bar(something); // This is an invalid call

As such, you're not implementing the contract you said you would.

In this situation, you have two simple options:

  • Adjust IFoo to be generic, and accept a T that is a derivation of IBase:

    public interface IFoo<T> where T : IBase { void Bar(T val); }
    public class Foo : IFoo<IChildA> { public void Bar(IChildA val) {} }
    

    Of course, this means that Foo can no longer accept any IBase (including IChildB).

  • Adjust Foo to implement IFoo, with an additional utility method for void Bar(IChildA val):

    public class Foo : IFoo
    {
        public void Bar(IBase val) {}
        public void Bar(IChildA val) {}
    }
    

    This has an interesting side-effect: whenever you call ((IFoo)foo).Bar it will expect IBase, and when you call foo.Bar it will expect IChildA or IBase. This means it satisfies the contract, while also having your derived-interface-specific method. If you want to "hide" the Bar(IBase) method more, you could implement IFoo explicitly:

    void IFoo.Bar(IBase val) { }
    

    This creates even more inconsistent behavior in your code, as now ((IFoo)foo).Bar is completely different from foo.Bar, but I leave the decision up to you.

    This means, with the second version in this section, that foo.Bar(new ChildB()); is now invalid, as IChildB is not an IChildA.


Why can't I implement this by passing a more derived interface which does implement the base requirement. Whatever instance would be passed it would still implement the base, am I missing something?

This is not allowed because of the reasoning I mentioned above, IFoo.Bar expects any IBase, whereas you want to further constrain the type to IChildA, which is not a super-interface of IBase, and even if it were it would not be allowed because it violates the interface implementation, though you could more easily define a second method at that point that does what you want.

Keep in mind that when you implement an interface, you subscribe to a contract, and C# will not let you violate that contract.

Upvotes: 4

Gilad Green
Gilad Green

Reputation: 37281

From MSDN:

When a class or struct implements an interface, the class or struct must provide an implementation for all of the members that the interface defines

This method in the derived

void DoSomething(IChildInterface baseInterface)

Does not have the same signature as the one in the interface:

void DoSomething(IBaseInterface baseInterface)

IChildInterface and IBaseInterface are not the same types. Therefore your derived class does not implement all methods of the interface and you get the compilation error.

For a possible the logic behind having this as a restriction instead of the compiler understanding the inheritance see Liskov's substitution principle as in SLakes answer

Upvotes: 2

SLaks
SLaks

Reputation: 888047

This violates the Liskov substitution principle.

ISomeInterface guarantees that the method can be called with any IBaseInterface instance. Your implementation cannot limit that to only accept IChildInterface interfaces.

Upvotes: 3

Related Questions