1

I have this code, the question is below the code:

public class MainProgram
{
    public static void Main()
    {
        List<Animal> Animals = new List<Animal>();
        Animals.Add(new Dog());
        Animals.Add(new Poodle());
        Animals.Add(new Poodle());
        Animals.Add(new Beagle());
        Animals.Add(new Cat());

        Bark[] JustTheBarks = Dog.GetBarkList(Animals);
        foreach (Bark B in JustTheBarks)
        {
            Console.WriteLine(B.ToString());
        }
    }
}

abstract class Animal
{
    public abstract Noise GetNoise();
}

class Dog : Animal
{
    public override Noise GetNoise() 
    {
        return new Bark("bark");
    }

    public static Bark[] GetBarkList(List<Animal> List)
    {
        return List
            .OfType<Dog>()
            .Select(r => r.GetNoise())
            .Cast<Bark>()
            .ToArray();
    }
}

class Beagle : Dog
{
    public override Noise GetNoise()
    {
        return new Woof("woof", 7);
    }
}

class Poodle : Dog
{
    public override Noise GetNoise()
    {
        return new Purr();
    }
}

class Cat : Animal
{
    public override Noise GetNoise()
    {
        throw new NotImplementedException();
    }
}

class Noise
{
}

class Bark : Noise
{
    protected string Text;
    public Bark(string Text)
    {
        this.Text = Text;
    }

    public override string ToString()
    {
        return $"{Text}";
    }
}


class Woof : Bark
{
    protected int Pitch;

    public Woof(string Text, int Pitch) : base(Text)
    {
        this.Pitch = Pitch;
    }
    public override string ToString()
    {
        return $"{Text}->{Pitch}";
    }
}

class Purr : Noise { }

}

In plain text, Animals and Noises, each animal returns its own type of noise, the noises correspond to the animal class, although sometimes an animal might return different Noises based on some variable (but for a dog only some form of Bark).

This code crashes of course. The Poodle returns a Purr(), and it "should" return something of type Bark. I have the abstract function GetNoise() which just returns a Noise. What I want/need is to write

class Dog
{
    public override Bark GetNoise();
}

But this is not allowed, the return type must be the same. I don't want to write a second layer of GetBark() functions in every Dog class, etc.

What I want is to re-write this such that there is no casting, and everything that is derived from Dog is forced to return a Bark in its GetNoise() function such that I can safely write:

Bark B = Dog.GetNoise();
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
David
  • 1,743
  • 1
  • 18
  • 25
  • why cant you simply say the Dog::GetNoise returns a Noise. – pm100 Nov 16 '17 at 21:54
  • 1
    Can't answer anymore because the question is locked, but a easy solution is to seal the overload then make a new protected overload that returns `Bark` that your children override. Here is a link to a fiddle of it working https://dotnetfiddle.net/gZFe0n – Scott Chamberlain Nov 16 '17 at 21:56
  • @ScottChamberlain You mean the exact thing shown in the duplicate... – Servy Nov 16 '17 at 21:57
  • To get compile time enforcement you probably looking for `sealed override` (https://stackoverflow.com/questions/13858384/sealed-keyword-in-association-with-override) paired with forwarding implementation to virtual DogNoise similarly as suggested in duplicate. Still can't help you with ability to get bark from dog so. – Alexei Levenkov Nov 16 '17 at 21:58
  • @Servy I don't see a single example in the duplicate that mentions sealing the class then using a new protected override. Can you link to it if I am just blind and missing it. the closest I could find was [this one](https://stackoverflow.com/a/157128/80274) execept that answer still has the same problem as the OP where a poodle could meow. – Scott Chamberlain Nov 16 '17 at 21:59
  • @ScottChamberlain Look at [the highest scored answer](https://stackoverflow.com/a/157137/1159478). It does the same thing you're doing. – Servy Nov 16 '17 at 22:00
  • @Servy the highest scored answer does not provide compile time safety, you could do https://dotnetfiddle.net/4yAWhq and it compiles fine. What the OP is looking for is compile time safety. – Scott Chamberlain Nov 16 '17 at 22:04
  • 1
    I feel the linked duplicate is not the same question. The dup wanted to change the exterior type presented to the user, the OP is looking for class internal compile time safety. I am reopening it. – Scott Chamberlain Nov 16 '17 at 22:06
  • @ScottChamberlain So you want to do *literally exactly what's in the duplicate*, but just change which method calls which. It's still exactly the same problem, and not a meaningful difference in the solution. If you think that your alteration is superior, then post it to the canonical, rather than reopening an exact duplicate question just to re-post the solution. That's the whole point of closing duplicates, to not have the same solution re-posted all over the place. – Servy Nov 16 '17 at 22:11
  • @scott consider editing question with why duplicate you reopened did not answer this one. – Alexei Levenkov Nov 16 '17 at 22:14
  • @Servy But they are different situations. My solution does not solve the problem in the duplicate, They wanted the derived class returned from the public method. This question does not care that only a Animal is returned via the public method, what it does care about that people derived from Dog must be forced by a compile time error return a Bark up to the level of Dog's protected classes so it can be used by it's own internal methods. – Scott Chamberlain Nov 16 '17 at 22:15
  • @AlexeiLevenkov I feel the content of the question makes it stand out as a seperate question, it just had a title that made it look like a duplicate. I have changed it, David if you disagree with the change feel free to change it. – Scott Chamberlain Nov 16 '17 at 22:19

3 Answers3

3

The answer of Scott Chamberlain is entirely reasonable; I would add two things to it.

First, the feature you want is called "virtual method covariance". It is supported by C++, but not by C#. It has been a fairly frequently requested feature since the early days of C#, but has never been implemented because, well, frankly, it's not a great feature:

  • Though type-safe in simple scenarios, it introduces new kinds of brittle base class failure in more complex, versioned scenarios
  • The CLR does not support it natively, so the compiler would have to generate helper methods, which are not particularly efficient
  • You can emulate the feature by implementing the helper methods yourself

If you want it, go advocate for it on the Roslyn github forum. You won't be the only one.

Second, you might consider in addition to Scott's solution to implement

public new virtual Bark GetNoise() => GetBark();

As you note, you may not fill an existing virtual method slot with any method that does not exactly match the signature and return type. But you certainly may introduce a new virtual slot with a different return type.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
2

The way to do this is you seal the overload at the Dog level then make a new protected method that the children must implement.

using System;
using System.Collections.Generic;
using System.Linq;

public class MainProgram
{
    public static void Main()
    {
        List<Animal> Animals = new List<Animal>();
        Animals.Add(new Dog());
        Animals.Add(new Poodle());
        Animals.Add(new Poodle());
        Animals.Add(new Beagle());
        Animals.Add(new Cat());

        Bark[] JustTheBarks = Dog.GetBarkList(Animals);
        foreach (Bark B in JustTheBarks)
        {
            Console.WriteLine(B.ToString());
        }
    }
}

abstract class Animal
{
    public abstract Noise GetNoise();
}

class Dog : Animal
{
    public sealed override Noise GetNoise() 
    {
        return GetBark();
    }

    protected virtual Bark GetBark()
    {
        return new Bark("bark");
    }

    public static Bark[] GetBarkList(List<Animal> List)
    {
        return List
            .OfType<Dog>()
            .Select(r => r.GetBark()) //Now calls GetBark() instead of GetNoise()
            .ToArray();
    }
}

class Beagle : Dog
{
    protected override Bark GetBark()
    {
        return new Woof("woof", 7);
    }
}

class Poodle : Dog
{
    protected override Bark GetBark()
    {
        return new Purr(); //This is now a compiler error.
    }
}

class Cat : Animal
{
    public override Noise GetNoise()
    {
        throw new NotImplementedException();
    }
}

class Noise
{
}

class Bark : Noise
{
    protected string Text;
    public Bark(string Text)
    {
        this.Text = Text;
    }

    public override string ToString()
    {
        return $"{Text}";
    }
}


class Woof : Bark
{
    protected int Pitch;

    public Woof(string Text, int Pitch) : base(Text)
    {
        this.Pitch = Pitch;
    }
    public override string ToString()
    {
        return $"{Text}->{Pitch}";
    }
}

class Purr : Noise { }
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • I think if you add one more class that `new virtual Bark` and implements `GetBark` to call this new method you can even get exact syntax asked in the question. – Alexei Levenkov Nov 16 '17 at 22:12
  • Note that without applying my suggestion of extra class one still can't `Bark b=dog.GetNoise()`... – Alexei Levenkov Nov 16 '17 at 22:27
  • @AlexeiLevenkov If `dog` is a `Dog` and not your class under dog that has `new virtual Bark` in it you can't do it with your method either. – Scott Chamberlain Nov 16 '17 at 22:29
  • `Dog:DogBaseThatAddsGetBark`... But as deltocs answer suggests building complicated hierarchy more of theoretical value and some other solution is likely more appropriate. – Alexei Levenkov Nov 16 '17 at 22:56
-1

This is a classic mis-application of OO.

Beagle and Poodle are not sub-classes of "Dog". Rather they are collectively different types of dog that share similar characteristics. They both have the same properties but the values of those properties identify groups of similar instances not sub-classes.

This is similar to the confusion of "Square" and "Rectangle" as sub-classes of shape when in fact they are both instances of a polygon. The properties they have in common (4 vertices, 4 equal internal angles of 90 degrees) do not constitute different classes of shape but different values of the properties of those shapes.

In your example the issue extends deeper into the differentiation of different characteristics of noise as sub-classes. A Purr and a Bark are both instances of a noise differentiated only by the waveform and/or the method of production.

What is missing is the nature of the noise (vocal, sub-vocal or other - e.g. insects rubbing legs/wing-cases together etc) or the purpose of the noise (warning, pleasure, anger, anxiety, pain, attracting a mate etc)

The reduction of noise making on an animal to a single method then compounds the problem.

Assuming that Cat and Dog are (more) legitimate subclasses of Animal: Cat's have the ability (a method) to Purr() but a Dog does not. A Dog has the ability to Bark() but a Cat does not. A Cat() has the ability to Meow() however and in some cases a Meow() might be considered the equivalent to a Bark() but in other cases a Hiss() might be more nearly equivalent. Yet again, when a Dog Growls() is this akin to a Cat Hiss()ing or Purr()ing (physiologically the latter, contextually the former) ?

And then there is Yelp()ing. Howl()ing. etc.

All of these are noises and independent of any particular animal type involved there is no legitimate basis for saying that if some thing is capable of making any Noise then it can only make specific sub-classes of Noise.

In short: This object model is broken. :)

Rather than try to contort the classes to fit a broken model it will serve you better in the long run to fix the model itself.

HOW to fix it is almost infinite in variety.

You might have specific methods for specific noises. You might compose those methods as members of an interface. The methods or the interfaces might be organised around the nature of the noise (IBark, IYelp, IPurr etc)

IBark.Bark()
IYelp.Yelp()

Or may have methods that are oriented around collections of behaviours (IExpressPain, IExpressHappiness):

List<EmotionalExpression> IExpressPain.ExpressionsOfPain()
List<EmotionalExpression> IExpressPain.ExpressionsOfHappiness()

Or these methods might add expressions to some specified and provided collection:

IExpressPain.ExpressIrritation(List<EmotionalExpression> expressions)

Where an EmotionalExpression might be some representation of (e.g.) Wag, Twitch, Purr, Growl etc. Where Wag and Twitch are subclasses of TailExpression and Purr and Growl are subclasses of SubVocalExpression (in turn a sub-class of AudibleExpression) etc etc. So for any particular emotionally driven behaviour an animal might express any number of different emotional expressions of different classes (physical, audible etc).

It all depends on the needs and acceptable complexities of the application.

But what I think can be said is that GetNoise() is an over-simplification and hence your difficulty.

Deltics
  • 22,162
  • 2
  • 42
  • 70
  • Good answer! I did not even think about that for mine. – Scott Chamberlain Nov 16 '17 at 22:32
  • So, replace all instances of "Animal" with ClassA and all instances of Dog with ClassB, etc, and rewrite your answer. Trying to go into the acoustic qualities of bark and purr? Where are you going with this? How do you know this is not a game where a bark gives 5 points and a purr only gives 3 points. I was attempting to write a minimally complete example, not create Animal-Sim. – David Nov 16 '17 at 22:56
  • I just found this link while doing more research, it is **scarily** similar to my OP, seems that using animal/dog/bark is a common when illustrating this problem, https://colinmackay.scot/2008/10/10/method-hiding-or-overriding-or-the-difference-between-new-and-virtual/ – David Nov 16 '17 at 23:20
  • That a mistake is commonly made doesn't mean it isn't a mistake. ;) . Examples exist to illustrate specific points - this does not make them examples of best or even good practice. However, even in this case you will note that Cat/Dog do not inherit a "Make Noise" method from Mammal. The entire example is a contrivance to illustrate virtual methods and overrides but in doing so re-enforces the mis-application of OO to differentiate by class that which is distinctive by property: The type/quality of noise emitted is a property of a particular Dog, not a whole new way of Bark()ing. – Deltics Nov 17 '17 at 01:00
  • That's a poor assumption to make. I could easily (and this is much more likely) be writing some type of video game where the only animals are the ones listed, and there is no concept of 'mammal'. Further, they would differentiate by class, because the Beagle class (not the Poodle class) would have the interface IWW1FlyingAce. – David Nov 17 '17 at 15:04