0

I'm following a MOOC, and in it I have the following code. First, two types of classes :

class EasyMonster
{
    //stuff
}

and

class DifficultMonster : EasyMonster
{
    //same stuff, just overrode some functions
}

In the main program, the function below :

private static EasyMonster MakingMonster()
{
    if stuff
        {
        return new EasyMonster();
        }
    else
        {
        return new DifficultMonster();
        }
}

What I understood of the C# theory is that the result of this function should be treated as an EasyMonster-type object, and there should be no way for the program to keep in mind that sometimes the result of the function MakingMonster() was, at the beginning, of the DifficultMonster-type.

Nevertheless, in the rest of the code, I find this :

EasyMonster monster = MakingMonster();
        // Blah Blah
if (monster is DifficultMonster)
    accDiff++;
else
    accEasy++;

Can someone explain ?

EDIT : What I don't understand is why the EasyMonster part of the line private static EasyMonster MakingMonster() doesn't force the type of the variable returned to be EasyMonster. Why is it legal ?

Doe Jowns
  • 184
  • 1
  • 3
  • 12
  • 2
    This is nonsense: `DifficultMonster : EasyMonster`. Instead both should inherit from `Monster`(or implement the same interface `IMonster`). – Tim Schmelter Jul 24 '17 at 08:36
  • When you return `DifficultMonster` as `EasyMonster` it is still `DifficultMonster`. You just "hide it". You can see for yourself. Do this: `if (monster is DifficultMonster) { ((DifficultMonster)monster).MethodFromDifficultMonster() }` – FCin Jul 24 '17 at 08:39
  • You do always have an object of the original type (easy or difficult) but the rest of the code doesn't need to know that. So keep the `accDiff` value inside the monster object and let it keep it's own track of the values. – DavidG Jul 24 '17 at 08:40
  • I agree with @TimSchmelter. See Inheritance as "Is-A" relationships and ask yourself those "Is-A" questions. Example: Is a `DifficultMonster` a `EasyMonster`? Surely not. Is a `DifficultMonster` a `Monster`? Yes, it is. – jAC Jul 24 '17 at 08:40
  • "to keep in mind that sometimes the result of the function MakingMonster() was, at the beginning, of the DifficultMonster-type." could you rephrase this part? what exactly do you mean by "in the beginning"? – Mong Zhu Jul 24 '17 at 08:41

2 Answers2

1

If a class inherits from a base class you can always define a method that has a signature (reminder, that is the method head) that returns an object of the base class-type.

Since a derived class inherits all components from the base class, it can effectively be treated as the base class just as well. The runtime will always find an object, that has compatible properties, methods and so on.

So compilerwise it is perfectly fine and will never break to return an instance of a base class, even if you deliver a derived class in the implementation.

What I understood of the C# theory is that the result of this function should be treated as an EasyMonster-type object, and there should be no way for the program to keep in mind that sometimes the result of the function MakingMonster() was, at the beginning, of the DifficultMonster-type.

So this is not entirely correct. The syntax says, that whatever object is returned here will comply with the definition of a EasyMonster-object. It does not need to be an EasyMonster in itself, only that somewhere in the inheritance tree there is in fact the class EasyMonster. The returned object however does retain all information it was constructed with. (Since it is a reference type, you only get a reference to the object anyways. So it is pointing to an instance of DifficultMonster).

What I don't understand is why the EasyMonster part of the line private static EasyMonster MakingMonster() doesn't force the type of the variable returned to be EasyMonster. Why is it legal ?

Returning or casting an object to a base type does not strip away the "excess" part that was added during inheritence. It merely says that the object should now be treated as the base type for the runtime.

That also means, you have to be careful with overriding stuff that wasn't meant to be overridden in the first place. You can always override things (methods, properties, events etc.) with the new keyword, but that can have unexpected results if you do not know how overriding works. For more detail, look here or here.

The is keyword just checks if the object that your reference points to is compatible to the type description at runtime, see here. If you use the inheritance tree supplied by your classes, DifficultMonster is both EasyMonster and DifficultMonster.

If you want to derive by your class structure that your classes are indeed not the same, than you have to introduce a base type from which both classes inherit like say Monster:

class Monster
{
     // implement all that monsters have in common.
}

and then you derive your EasyMonster and your DifficultMonster from your base class Monster

class EasyMonster : Monster 
{ 
    // implement specific things for the easy monster.
}

class DifficultMonster : Monster
{
    // implement specific things for the difficult monster.
}

The method would then return an object of Type Monster:

private static Monster MakingMonster()
{
    if stuff
    {
        return new EasyMonster();
    }
    else
    {
    return new DifficultMonster();
    }
}

And now the check will only be true if it is really a DifficultMonster and not an EasyMonster.

Monster monster = MakingMonster();
    // Blah Blah
if (monster is DifficultMonster)
    accDiff++; // only counts if the object is DifficultMonster
else
    accEasy++; // only counts if the object is not a DifficultMonster

Since you probably don't want anyone to create an instance of your base class, because it doesn't make any sense having an undefined monster sitting around, you can then design the Monster class to be abstract. Then only the derived classes can be instanced.

abstract class Monster { }

private static Monster MakingMonster()
{
    return new EasyMonster(); // still works.
}

private static Monster MakingMonster()
{
    return new Monster(); // will create compiler error.
}
Adwaenyth
  • 2,020
  • 12
  • 24
0

Ok, I found elements of answer :

it seems :

1) that

private static EasyMonster MakingMonster()
{
if stuff
    {
    return new EasyMonster();
    }
else
    {
    return new DifficultMonster();
    }
}

is equivalent to :

private static EasyMonster MakingMonster()
{
if stuff
    {
    return ((EasyMonster)new EasyMonster());
    }
else
    {
    return ((EasyMonster)new DifficultMonster());
    }
}

and that returning (EasyMonster)stuff with "stuff" a DifficultMonster-type object returns something like "a DifficultMonster-type object for the moment casted in EasyMonster-type but which still can be converted in his original type"

Source (in French, sorry guys <3) : https://openclassrooms.com/courses/programmez-en-oriente-objet-avec-c/la-poo-et-le-c-1 § : "La conversion entre les objets avec le casting"

Doe Jowns
  • 184
  • 1
  • 3
  • 12