-4

I am trying to understand the logic behind some C# casting conditions for classes, This is my testing code

File: example.cs

public class Animal { public string animal_name = "Animal"; }

public class Dog : Animal { public string dog_name = "Dog"; }

public class Class1
{
    public void createObjects()
    {
    var animal1 = new Animal();
    printAnimalName(animal1);
    }

    public void printAnimalName(Animal my_animal)
    {
    var dog1 = my_animal as Dog; // dog1 is of type Dog
    Console.WriteLine(dog1.dog_name);
    }
}

In my Main function, I call the call createObjects function as follows:

    static void Main(string[] args)
    {
        Class1 c1 = new Class1();
        c1.createObjects();
        Console.ReadLine();
    }

Running the code above gives an error

System.NullReferenceException:'Object reference not set to an instance of an object'

I understand that this is the way it should be, due to the casting in:

var dog1 = my_animal as Dog;

But what is the logic behind it? Why can't we call the function printAnimalName by passing an Animal object for it? This should be possible as per my understanding, because the function expects an Animal object.

Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
Sameer Damir
  • 1,454
  • 2
  • 11
  • 8
  • 3
    animal1 is of type Animal, not of type Dog. Try to make Animal an abstract class, and see what happens. You should use it like: var animal1 = new Dog(); – L-Four Feb 15 '18 at 13:05
  • 1
    `as` will try to cast it, and if it can't it will return `default`. – Daniel A. White Feb 15 '18 at 13:05
  • 1
    `if (my_animal is Dog dog1) Console.WriteLine(dog1.dog_name); else Console.WriteLine("Not a dog!");` with newer c# the casting via `is` is preferred. With `as` you need to cast && test, with `is` you get the testing included. – Patrick Artner Feb 15 '18 at 13:05
  • It will work if you only try to use what is known about `Animal` and not try to cast to `Dog`. If you need a `Dog` then the parameter to the method should take a `Dog` and not an `Animal`. – juharr Feb 15 '18 at 13:06
  • @juharr not true, parameter should be Animal, that's the whole point of polymorphism. – L-Four Feb 15 '18 at 13:07
  • [which-is-the-best-practice-in-c-sharp-for-type-casting](https://stackoverflow.com/questions/32776436/which-is-the-best-practice-in-c-sharp-for-type-casting) and read the "dupe" markers as well. – Patrick Artner Feb 15 '18 at 13:10
  • 1
    Your example is not so good. First, a dog will have both `dog_name` and `animal_name`. Second, if you want to handle a `dog` instance in your method, you should pass a `dog` instance to it. No point of taking a base class as an argument if inside the method you can only use the derived class. – Zohar Peled Feb 15 '18 at 13:26
  • @L-Four I mean that a design where you cast a less specific type to a more specific type is often a bad design. Also there is no polymorphism here since nothing is overriden. – juharr Feb 15 '18 at 13:26

3 Answers3

3

After that var dog1 = my_animal as Dog; // dog1 is of type Dog you need to add only null check:

if(dog1 != null)
{
    Console.WriteLine(dog1.dog_name);
}
kasuocore
  • 99
  • 1
1

I think you need to learn about polymorphism, abscract classes and interfaces.

public abstract class FourLeggedAnimal
{
    public int GetLegCount()
    {
        return 4;
    }
}

public class Dog : FourLeggedAnimal
{
    public string GetScientificName()
    {
        return "doggus scientificus";
    }
}

public class Cat : FourLeggedAnimal
{
    public string GetServant()
    {
        return "human";
    }
}

public class AnimalInformer
{
    public void DisplayInformation(FourLeggedAnimal animal)
    {
        Console.WriteLine("It has {0} legs", animal.GetLegCount());

        if (animal is Dog)
            Console.WriteLine("Its scientific name is {0}", ((Dog)animal).GetScientificName());
        if (animal is Cat)
            Console.WriteLine("Its servant is {0}", ((Cat)animal).GetServant());
    }
}

Here you use the absract class to provide base functionality to all other classes derived from it. All classes derived from FourLeggedAnimal have a method GetLegCount() that returns the number of legs.

But a cat has a servant a dog doesnt have, it just has a friend(both humans, but different relations). So the dog needs no method "GetServant" but the cat does. -> Differenct implementations in 2 seperate classes

Another example with interfaces is that each derived class needs to provide that functionality.

public interface IMovableObject
{
    int GetMaxSpeed();
}

public class Car : IMovableObject
{
    public int GetMaxSpeed()
    {
        return 100;
    }
}

public class Human : IMovableObject
{
    public int GetMaxSpeed()
    {
        return 20;
    }
}
public static class SpeedChecker
{
    public static void CheckSpeed(IMovableObject speedster)
    {
        Console.WriteLine("Checking Speed..");

        int speed = speedster.GetMaxSpeed();
        if (speed > 50)
            Console.WriteLine("It's really fast!");
        else
            Console.WriteLine("Just a turtle or something similar...");
    }
}

Now, if you have a Method getting a IMovableObject that is actually a car, you call the implementation of Car:

Car c = new Car();
Human h = new Human();

Console.WriteLine("Checking Car..");
SpeedChecker.CheckSpeed(c);
Console.WriteLine("Checking Human..");
SpeedChecker.CheckSpeed(h);

-> returns:

Checking Car...
Checking Speed...
It's really fast!
Checking Human...
Checking Speed...
Just a turtle or something similar...

These are 2 uses where you derive classes and use castings to get certain functionality or use the basetype without casting but still getting different functionality

Chrᴉz remembers Monica
  • 1,829
  • 1
  • 10
  • 24
0

Your problem is here:

public void printAnimalName(Animal my_animal)
{
  var dog1 = my_animal as Dog; // dog1 is of type Dog
  Console.WriteLine(dog1.dog_name);  //Animal does not have this property!
}

Casting does not invoke a constructor. This means that dog_name is null, as my_animal does not have a dog_name property. I think you missed something on inheritance here.

This is actually an example that happens in more complex form in the real world; Given class A, B inherits from A. Both have the same properties because of inheritance. Then someone makes a different property with a similar, but not congruous property name and uses it for the same function in the child object. Welcome to code smell city.

To fix your function so it comes across as a dog, you'd do two things:

    public class Animal { public string animal_name = "Animal"; }

    //since we want a different default, we can 
    //can make the change in the constructor
    public class Dog : Animal 
    {  
        Dog(){ this.animal_name = "Dog"; }
        //if you really, really want a second name string, you can do this:
        public string Dog_Name 
        {
           get { return this.animal_name; } 
           set { this.animal_name = value; }
        }
    }

Then, you need to make your method call the appropriate property.

  public void printAnimalName(Animal my_animal)
  {
    var dog1 = my_animal as Dog; // dog1 is of type Dog
    Console.WriteLine(dog1.animal_name);
  }

I also recommend changing your public fields to properties and possibly override ToString() when all you want to do with an object is return a string representing it.

CDove
  • 1,940
  • 10
  • 19
  • It isn't that `dog_name` is `null`, it's that `dog1` is `null`. – juharr Feb 15 '18 at 13:28
  • 1
    You **defiantly don't** want to use the `new` keyword here. If you do, you will get the base class implementation when the reference is of the base class type. [See this demo.](http://rextester.com/MZI4977) – Zohar Peled Feb 15 '18 at 13:30
  • I dropped the New keyword, because you're right. Replaced it with how to call a field from a property, because the querant *needs* to start using properties. – CDove Feb 15 '18 at 13:34
  • Why should such a method fail if passed another sort of animal? This implementation fails unless it is given a dog. – Aluan Haddad Feb 15 '18 at 14:00
  • I was limiting the response to the scope of the question. That is, indeed, an issue. If it's not a `Dog` or derived from `Dog` and there is no other acceptable input, an exception will be thrown. Exception handling, however, is done based on where you need it handled and how, so it's outside the scope of the question. – CDove Feb 15 '18 at 14:11