3

Interface methods in C# can be implemented explicitly, so that their implementation is invoked when an instance is explicitly cast to the interface type. Why is this not also supported on virtual methods of classes?

Although working around the 'multiple inheritance' issue is unique to interfaces, it seems that for every other reason that explicitly implemented members would be useful for interfaces, they would also be useful for virtual methods. A cleaner return-type covariance model springs to mind.

Edit: By request, an example:

public class Foo {
    ...
}

public class Bar : Foo {
   ...
}

class Base {
   abstract Foo A ();
}

class Dervied {
  private Bar _b;

  Bar A () {
    return _b;
  }

  Foo Base.A () {
    return _b;
  }
}

I am aware of using helper methods to simulate this, but the net effect seems to have any of the bad characteristics that explicit implementation would have, but with a dirtier API. The crux of my question is not how to do return type covariance, but why a similar mechanism for interfaces is not supported for virtual methods.

Justin Aquadro
  • 2,280
  • 3
  • 21
  • 31
  • 1
    Could you explain how you envision this working? If `Derived` extended `Base`, you're looking for a virtual method on `Base` only being called on implementations of `Derived` cast to `Bar`? I don't see the utility in this form of information hiding. An example would help. – user7116 Jul 03 '11 at 16:12
  • 1
    Explicit interface implementation is a bandaid. You *have* to use it to resolve the ambiguity that's introduced by being able to inherit multiple interfaces that may have members with the same signature. Which is the standard problem of multiple inheritance, minus the problem of having to select between multiple inherited implementations. A problem that doesn't exist in C# because interfaces cannot define an implementation. Thus no need for explicit method overriding, there's only one base implementation to choose from. – Hans Passant Jul 03 '11 at 16:24
  • Your example uses `abstract` method, not a `virtual` one. Could you be more precise in your question? – Jakub Konecki Jul 03 '11 at 16:41
  • Abstract methods are virtual implicitly and closer to a member defined in an interface, which is my comparison. My question is intended to cover both cases, however. – Justin Aquadro Jul 03 '11 at 16:46

2 Answers2

5

Some people recommend not having public virtual methods in the first place. But instead create one public non virtual method representing the consumer interface, and one protected virtual method representing the implementer interface.

I would not call separating the contracts for caller and implementer "muddying the design". In many cases it's cleaner IMO, but I'm usually too lazy to actually do it that way.

This design works much better with return type covariance and method hiding.

An additional benefit of this is that the public wrapper can add additional checking code and supports different contracts for the caller and implementer.

An example of how I'd emulate return type covariance:

public class Base
{
    protected virtual Base FooOverride(int i){return new Base();};//FooOverride does not need to duplicate the argument checking

    public Base Foo(int i)
    {
        if(i<0)
          throw new ArgumentException("i<0");
        return FooOverride(i);
    }
}

public class Derived:Base
{
    protected override Base FooOverride(int i){return new Derived();};
    public new Derived Foo(int i)
    {
        return (Derived)base.Foo();
    }
}
CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • This is a very useful design. I wish .NET had used it for some of their `System` libraries. Is there a name for this design pattern? – Mr Anderson Nov 29 '16 at 22:33
3

What benefit would that have, besides from allowing something like this?

class Base
{
    virtual void M() { }
}

class Derived : Base
{
    override void M() { }

    override void Base.M() { }
}

This effectively bakes a violation of the Liskov Substitution Principle into the C# language - if I have a variable of type Base, calling M() on it can do entirely different things depending on whether the run-time type is Base or Derived.

Explicit interface implementation is different. Say you have this:

interface IFoo
{
    void DoStuff();   
}

interface IBar
{
    void DoStuff();
}

class C : IFoo, IBar
{
    void IFoo.DoStuff() { }

    void IBar.DoStuff() { }
}

This preserves the LSP - if I have an IFoo variable that happens to be of run-time type C, calling DoStuff() on it will get the IFoo implementation of it. Likewise with IBar.

Zack Elan
  • 1,716
  • 2
  • 16
  • 23
  • 3
    One common pattern is return type covariance as the OP mentioned. You add a new method that's equivalent to the `virtual` function except it returns a more specific type. – CodesInChaos Jul 03 '11 at 16:10
  • Explicit implementation can be used to support return-type covariance without muddying up an API with hidden virtual helper methods (which destroys the primary interface in the same way that explicit implementation would, by making the primary base and override unrelated). Interestingly, return type covariance is one of the requirements your link mentions, yet is not natively supported. – Justin Aquadro Jul 03 '11 at 16:11
  • 1
    Suppose I have an abstract base class ReadableFoo, with an abstract read-only property Bar. I would like to have a class MutableFoo, derived from ReadableFoo, with a read-write property Bar. Is there any way to create such a class without having to add an extra layer of inheritance whose purpose is to override the read-only property "Bar"? For that matter, would there be any way to construct a project which exposed ReadableFoo and MutableFoo, described as above, without exposing any other classes? Note that ReadableFoo and MutableFoo fully obey the LSP. – supercat Jul 06 '11 at 20:03
  • @supercat: Having MutableFoo inherit from ReadableFoo seems weird to me. It _feels_ like a misuse of inheritance (but you're absolutely correct that it doesn't break the LSP). I think what I'd do instead is have ReadOnlyFoo hold a `private readonly` reference to the MutableFoo, and delegate all the property accesses to that instance. Yes, you have to update in 2 places whenever you add/remove/change a property, but you'd have that same problem using inheritance as well. I would also look at **why** there needs to be a ReadOnlyFoo and a MutableFoo - that seems like a code smell. – Zack Elan Jul 08 '11 at 02:11
  • @Zack Elan: The reason for having both ReadableFoo (not ReadOnlyFoo!) and ImmutableFoo is that some routines will want to accept a Foo which they can read from now, but won't care about whether it may change later. Others may want a Foo that they know will never change. One could have a concrete implementation of read-only properties within a ReadableFoo, but that presupposes that all derived classes will want to store their information in the same backing fields--an assumption that may hold true for some classes, but not all. As a simple example... – supercat Jul 08 '11 at 13:32
  • @Zack Elan: One may have a family of variations on ReadableFoo where certain fields are fixed to hard-coded values and thus don't need to be stored. Having a single redundant "empty" ReadableFoo wouldn't be worth worrying about, but if e.g. 90% of ReadableFoo instances would only have information in three out of sixteen fields, it may be worthwhile to have a derivative of ReadableFoo which doesn't include those fields. – supercat Jul 08 '11 at 13:34
  • @Zack Elan: BTW, a common pattern I follow is to have IReadableFoo contain methods "AsImmutable" and "AsMutable", each of which will either return the object or create a new one with identical data (depending upon whether the object is mutable or immutable). A class which wishes to persist a foo can use AsImmutable to make an immutable version if needed (if it's already immutable, it will simply return the existing object). – supercat Jul 08 '11 at 18:40
  • An object which holds a reference to a 'foo' that may either be a shared immutable object or an unshared mutable object, and which wants to replace that with a reference to a modified version of it, can call "AsMutable", mutate the returned reference, and then store the reference to the object returned by AsMutable (which may or may not be the same object). – supercat Jul 08 '11 at 18:46