'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 1:[1]

You could use dynamic to solve this, but be warned: dynamic can introduce a lot of overhead.

For example:

using System;
using System.Collections.Generic;

namespace Demo;

static class Program
{
    static void Main(string[] args)
    {
        var animals = new List<Animal>()
        {
            new Dog(),
            new Cat(),
            new Rabbit()
        };

        var handler = new Handler();

        foreach (var animal in animals)
        {
            handler.Handle(animal);
        }
    }

    public sealed class Handler
    {
        public void Handle(Animal animal)
        {
            handleImp((dynamic)animal);
        }

        void handleImp(Cat cat)
        {
            Console.WriteLine("Handling cat");
        }

        void handleImp(Dog dog)
        {
            Console.WriteLine("Handling dog");
        }

        void handleImp(Animal animal)
        {
            Console.WriteLine("Handling unrecognised animal");
        }
    }
}

public abstract class Animal
{
    public void Update() { }
}

public class Dog : Animal
{
}

public class Cat : Animal
{
}

public class Rabbit: Animal
{
}

The output of this is:

Handling dog
Handling cat
Handling unrecognised animal

Try it online

Solution 2:[2]

You don't need those extra Handle Methods,

abstract class Animal
{
  public void update() { }
  public virtual void Handle(Handler handler)=> handler.Handle(this);
}

class Dog
{
  public override void Handle(Handler handler)
  {
     Console.WriteLine("Dog handler");
     base.Handle(this); 
  }
}

public class Handler
{
  private Clinic clinic; //only the handler talks to the database
  public void Handle(Animal animal) => animal.Handle(this); 
}

Now when you call Handler.Handle(new Dog()), the call will be dispatched to dog.Handle. That's how Polymorphism works.

However, I believe the relationship should not be two ways until absolutely needed. You should know which one among them has the actual business logic.

Like does the Animal really needs to have an instance of handler? Or you can manage with just the Id instead of actual reference.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Matthew Watson
Solution 2