5

Please consider the following program:

using System;

public interface IFoo
{
    void DoFoo();
}

public class Bar: IFoo
{
    public void DoFoo() => Console.WriteLine("BAR!");
}

public class Baz: Bar, IFoo
{
    void IFoo.DoFoo() => Console.WriteLine("baz!");
}

class Program
{
  static void Main()
  {
    Baz baz = new Baz();
    baz.DoFoo();
    
    IFoo foo = baz;
    foo.DoFoo();
    
    Bar bar = baz;
    bar.DoFoo();
    
    IFoo foobar = bar;
    foobar.DoFoo();
  }
}

It gives the following output which I personally with my C++ background consider highly unexpected:

BAR!
baz!
BAR!
baz!

Having , IFoo in the declaration of Baz seems to be substantial, because otherwise void IFoo.DoFoo() doesn't compile.

Can someone please explain what is going on here (especially the last line)? And what should be done to prevent such behavior in real life? Should one avoid implementing from the same interface at all or there are some other rules to avoid problems?

UPD:

Looks like the principal problem here is not with "multiple inheritance" (which is not real multiple inheritance actually), but with the way interface methods can be implemented in C#. Namely, one can have two different implementations of the same method in the same class, one of which is explicit, another is implicit. E.g. this program:

using System;

public interface IFoo
{
    void DoFoo();
}

public class Bar: IFoo
{
    void IFoo.DoFoo() => Console.WriteLine("Foo!");
    public void DoFoo() => Console.WriteLine("BAR!");
}

class Program
{
  static void Main()
  {
    Bar baz = new Bar();
    baz.DoFoo();
    
    IFoo foo = baz;
    foo.DoFoo();
  }
}

prints

BAR!
Foo!

The trick with "multiple inheritance" just allows to introduce the explicit implementation from a derived class.

From my point of view this feature of C# is potentially dangerous, because if one implements a method of an interface, one usually expects the same method will be called no matter if it is invoked from the interface or from the class. And this is really the case if one implements everything only explicitly or only implicitly. But if both ways are used, this assumption is broken. So the moral seems to be:

  1. Don't mix implicit and explicit implementation of the same method if you don't have in mind to employ this strange effect for some purpose.
  2. Use explicit implementation in derived classes with caution.
5
  • When you cast it to IFoo it uses the explicit IFoo.DoFoo() implementation. When you cast it to Bar it uses the Bar.DoFoo() implementation. When you cast it to Baz there is no explicit Baz.DoFoo() implementation, so it uses the one that it inherited from Bar. The IFoo.DoFoo() explicitly does not add a DoFoo() entry point to Baz. This is what I would've expected. Commented Dec 28, 2021 at 13:14
  • 1
    Are you aware of this? Also, can you show your expected output? Commented Dec 28, 2021 at 13:16
  • The feature allows you to handle conflicting method names. As example you want to implement two interfaces that both share the same method signature. Commented Dec 28, 2021 at 13:16
  • 2
    Note that casting from Baz to Bar to IFoo is no different from casting from Baz to IFoo. In the end it's a IFoo reference of a Baz. You would see a different result if you had Bar bar = new Bar(); instead. Commented Dec 28, 2021 at 13:16
  • @Sweeper, Certainly I know about explicit interface implementation for I used it in the example. But it was designed for implementing methods with the same name from different interfaces. Here we have the same interface. I'd expect it either not compile as without , IFoo in Baz declaration or give always the same output. Current behavior is obviously a potential source of bugs. The main question is what to do to avoid such bugs. What is the principal evil here? Commented Dec 28, 2021 at 13:31

3 Answers 3

5

This is a difference in the explicit implementation (void IFoo.DoFoo()) vs the implicit implementation (public void DoFoo()). The compiler will use the explicit implementation first. If you provide both an explicit and implicit implementation then the difference becomes clear:

https://dotnetfiddle.net/7l9gIs


using System;

public interface IFoo
{
    void DoFoo();
}

public class Bar: IFoo
{
    public void DoFoo(){ Console.WriteLine("BAR!"); }
}

public class Baz: Bar, IFoo
{
    void IFoo.DoFoo(){ Console.WriteLine("baz explicit!"); }
    public new void DoFoo(){ Console.WriteLine("baz implicit!"); }
}

public class Program
{
  public static void Main()
  {
    Baz baz = new Baz();
    baz.DoFoo();
    
    IFoo foo = baz;
    foo.DoFoo();
    
    Bar bar = baz;
    bar.DoFoo();
    
    IFoo foobar = bar;
    foobar.DoFoo();
  }
}

Output

baz implicit!
baz explicit!
BAR!
baz explicit!
Sign up to request clarification or add additional context in comments.

Comments

0

Implicit implementations tend to be more common and more convenient for usage. They are less verbose and any usage of the concrete type will have the implementations of the members exposed. Implicit implementations don't include the name of the interface being implemented before the member name, so the compiler infers this. The members will be exposed as public and will be accessible when the object is cast as the concrete type.

Visit this link for more details https://www.pluralsight.com/guides/distinguish-explicit-and-implicit-interface-implementation-csharp

Comments

-1

The confusion is starting when you implement Baz from IFoo. Because Bar is already implements IFoo and Baz is the subclass of Bar. So, you dont need to do that.

In object oriented programming its not a best practice, in fact it is worst practice.

If you want to override DoFoo method, use the following code

public interface IFoo
{
    void DoFoo();
}

public class Bar : IFoo
{
    public virtual void DoFoo()
    {
        // do something
    }
}

public class Baz : Bar
{
    public override void DoFoo()
    {
        // override what you did in Bar class
    }
}

In your code, when you try to baz.DoFoo, in fact you are calling bar.DoFoo.Because you didnt override it. Its the problem.

1 Comment

Should be noted this would just result in the OPs code always calling the DoFoo in Baz since the underlying type is always a Baz.

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.