2

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)
        {
        }
    }
}

5 Answers 5

4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Great explanation and thank you for the workaround!
3

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.

1 Comment

While this is true in a more general way I think the actual reason here is that the C# compiler simply requires a 1-to-1 implementation. If the situation was reversed (ie. interface required child interface, implementation allowed base interface) then it wouldn't violate Liskov but this is not legal either.
2

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

Comments

0

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)
    {
    }
}

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.