assuming I have a base class called AnimalTypes
and 3 derived classes, all 4 containing only variables. What I want to do is to create a List or an Observable collection in which I can add any type of the 4 classes. The solution I thought about is to create an Observable collection of type object
, but in this case I can't access the variables. Right now I have only one class called Animals
, but in some cases I have to leave a lot of variables empty and I want to get rid of this in order to make my code cleaner. Any solutions for this kind of problem? What can I do/use?
Animals animals = LoadAnimals();
foreach(var animal in animals)
{
print(animal.noOfLegs);
print(animal.noOfWings);
}
I can do this with my "unclean" solution, but I want to achieve the same results with a cleaner solution. (For example not every animal has wings, so it would be a derived class called Birds
, but I want to print 0 in case it does not exist but most importantly I want to access the variables of the objects)
I tried creating an ObsrvableCollection, but I can't access the variables inside my 4 object types
CodePudding user response:
you have two options, you can either use inheritance using a base class or polymorphism using an interface.
public class Animal
{
public int NoOfLegs { get; set; }
public int NoOfWings { get; set; }
}
public class Dog : Animal
{
// more variables and methods specific to a dog
}
public class Cat : Animal
{
///
}
// list or observable collection
List<Animal> animals = new List<Animal>();
animals.Add(new Dog { NoOfLegs = 4, NoOfWings = 0 });
animals.Add(new Cat { NoOfLegs = 4, NoOfWings = 0 });
foreach(var animal in animals)
{
Console.WriteLine("Number of legs: " animal.NoOfLegs);
Console.WriteLine("Number of wings: " animal.NoOfWings);
}
Or like this
public interface IAnimal
{
int NoOfLegs { get; set; }
int NoOfWings { get; set; }
}
public class Dog : IAnimal
{
public int NoOfLegs { get; set; }
public int NoOfWings { get; set; }
// more variables and methods specific to a dog
}
public class Cat : IAnimal
{
public int NoOfLegs { get; set; }
public int NoOfWings { get; set; }
///
}
CodePudding user response:
You can create a List of the base type AnimalTypes
, and put into this list the objects which type inherits from AnimalTypes
.
Then, when you enumerate all the items of the list, you should check the actual type of the object using the 'is' instruction.
Finally, you can cast the object to his type with 'as' instruction, to be able to access all his properties.
public class AnimalType
{ }
public class Bird : AnimalType
{ }
public class Program
{
static void Main()
{
var list = new List<AnimalType>();
list.Add(new Bird());
foreach(var item in list)
{
if(item is Bird)
{
var bird = item as Bird;
// Access Bird properties
}
else if (...)
...
}
}
}
CodePudding user response:
I guess this is homework or some coursework, but anyway:
The road you'll want to go down would basically depend heavily on how probable change to the code is and how it will be used.
Example 1
You have
Animal ---> Bird |-> Cat |-> Dog
And you are sure (as can be) that this will stay forever and ever like this and it is used in one single place in the code base.
Go ahead and switch on runtime type.
Example 2
Same inheritance tree but Parent class as well as all of the leaf classes will be used heavily throughout the codebase, often so as in your example: You have a bunch of Animals but might have to take extra action for their specific "needs" if they are a specific Animal-Subtype.
Then you might consider a solution that's convenient for that usage pattern.
Example 3
Same inheritance tree but every two 2 weeks 3 species (== classes inheriting from Animal) will be added.
Then you might prefer a solution that doesn't involve touching hundrets of places in the codebase that need another case be added.
To wrap this up:
You can make it work in way more than a handful ways. But there is not the "one fits all" solution.
There are quite some design patterns and concepts that deal with this exact problem. Each fits a different usecase. So I'd recommend to take a dive into them.
"Composition over Inheritance" is out the window, I guess, since this seems to be coursework / homework.
This would also include using some different approach to modelling Animals' properties like using a Dictionary. Assuming you are bound to the "each class has a specific set of properties (implemented as C#-properties)".
"Double-Dispatch Visitor Pattern" - may be a little over the top for this example but doesn't hurt to know.
"Adapter" could be an Idea. But that would change your API.
Of course in such a little example you could opt for the rough tools: pattern matching / switch on runtime type ... (as already shown in various other answers)
CodePudding user response:
No don't do this, what you should do is use generic constraints to ensure type safety.
Have AnimalTypes
implement an interface, ex. IAnimal
and this interface can be deliberately blank
The reason we want to do this is so we can constrain on the interface for your CRUD methods
public T AddToMyCollection<T>(T animalType) where T : IAnimal { }
Now to access these, you can use switch on its typing to access the variables. Which will allow for an inblock cast in which you can now do what you want with the info, and this is a fully type safe operation, as you already constrained the generic to exactly the 4 types you want
public void PrintSomeVars<T>(T animalType) where T : IAnimal
{
switch (animalType)
{
case Dog dog: //this will *cast* it to the proper object you want
Console.WriteLine($"what da dog doin: {dog.CurrentlyDoing}");
break;
default:
break;
}
}
//switch expression version that returns a string rather than void
public void PrintSomeVars<T>(T animalType) where T : IAnimal =>
animalType switch
{
Dog dog => $"what da dog doin: {dog.CurrentlyDoing}",
//...
_ => string.Empty //or throw
}
Hopefully in the future we can get something like a legitimate Discriminated Union within C#. I highly advise you to look into how F# would handle this with a discriminated union, maybe someone can contribute this in their answer. Would be a very different process but much cleaner from what I can imagine