Search code examples
c#methodsoverloadingabstractderived

C# map a list of abstract class objects to derived class for method overloading


I am in a situation where I am iterating a List<Animal> and want a Handler class to handle each element according to its derived type. The way I'm doing it is weird and hard to follow. Is there a better way?

The purpose of the Handler class is to encapsulate the code for talking to the database, I don't want the animals to have any reference to it. I just want to point a hose of animals at the database and have square pegs go in square holes, and round pegs in round holes.

Thanks in advance.

abstract class Animal
{
  public void update() { }
  public virtual void Handle(Handler handler) { }
}

public class Dog : Animal
{
  public override void Handle(Handler handler) => handler.Handle(this); 
}

public class Cat : Animal
{
  public override void Handle(Handler handler) => handler.Handle(this); 
}

public class Handler
{
  private Clinic clinic; //only the handler talks to the database
  public void Handle(Animal animal) => animal.Handle(this); 
  public void Handle(Dog dog) { /* write dog fields to database */ }
  public void Handle(Cat cat) { /* write cat fields to database */ } 
}

public void main()
{
  List<Animal> animals = getAnimals(); 
  Handler handler = new Handler(); 
  foreach(var animal in animals)
  {
    handler.handle(animal); 
  }
}

Edit: I think my question is essentially answered here: Passing different type of objects through the same method.

It seems like instead of having a sender system that he passes data to, it's like he is taking data and building a system around it. That kind of structure would incorporate design principle violations no matter what you tried to build around it.

I think the answer is to move the database write into the factory that generates the animals. This means I can't prevalidate the entire set of animals before writing, but it cleans out all the indirection and potentially removes the animal and handler classes entirely.


Solution

  • In C#, starting from version 7.0, you can make use of pattern matching to handle each concrete instance separately.

    using System;
    using System.Collections.Generic;
    using System.Linq;
                        
    public class Program
    {
        public abstract class Animal
        {
          public void update() { }
          public virtual void Handle(Handler handler) { }
        }
    
        public class Dog : Animal {}
    
        public class Cat : Animal {}
        
        public class Handler
        {
          public void Handle(List<Animal> animals)
          {
            foreach (var animal in animals)
            {
                switch (animal)
                {
                    case Cat cat:
                        Handle(cat);
                        break;
                    case Dog dog:
                        Handle(dog);
                        break;
                    default:
                        throw new NotImplementedException();
                }
            }
          }
    
          public void Handle(Dog dog) { Console.WriteLine("Wrote a dog field"); }
          public void Handle(Cat cat) { Console.WriteLine("Wrote a cat field"); } 
        }
        
        public static void Main()
        {
            List<Animal> animals = new List<Animal>(){
                new Dog(),
                new Cat(),
                new Dog(),
            };
            
            Handler handler = new Handler();
            handler.Handle(animals);
        }
    }
    

    Alternatively you could use LINQ's OfType<T> method to filter out instances of each concrete class and handle them that way.