Home > database >  Creating a registry of objects that inherit from a single base class and querying them on any class
Creating a registry of objects that inherit from a single base class and querying them on any class

Time:12-22

I have simplified this problem for ease of understanding, but I have a bunch of entities in a world and I register each of them with a class that tracks them. Here are the entities.

public class Entity
{
    public int Id;
};

public class Animal : Entity
{

};

public class Dog : Entity
{

};

public class Vehicle : Entity
{

};

public class Submarine : Vehicle
{

};

public class Car : Vehicle
{

};

My registry class internally uses two dictionaries so I can query the entity based on ID or the type it is. I have also greatly simplified this class for this example.

public class EntityRegistry
{
    private Dictionary<int, Entity> _idMap;
    private Dictionary<Type, List<Entity>> _typeMap;
    
    public void RegisterEntity(Entity entity)
    {
        entity.Id = GetNextEntityId();
        _idMap[entity.Id] = entity;
        var type = entity.GetType();
        if (!_typeMap.ContainsKey(type))
        {
            _typeMap[type] = new List<Entity>();
        }
        _typeMap[type].Add(entity);
    }
    
    public T GetEntity<T>(Entity entity) where T : Entity
    {
        if (!_entityMap.TryGetValue(entity, out var instance))
        {
            return default;
        }

        return (T)instance;
    }
}

Ideally what I'd like to do is be able to ask for entities of type Submarine and get all submarines or query for type Vehicle and get all vehicles (submarines and cars). The problem is that I register them explicitly as their child class type. I'd even like to query for Entity and get all entities back regardless of their child type.

Is there a better way to handle this? I have thought about registering all types that are possible in a dictionary, (e.g. Entity, Vehicle, Car, etc.) and then when registering, checking to see if it's of that type and then registering them in each list that applies, but I feel like that's a whole bunch of casting and testing and there must be a better way.

If there is a way (and it's fast) to get all of the parent classes up to a certain level (Entity), that would also be another way I could register them.

CodePudding user response:

Given these classes (I fixed what Dog inherits from and added a subtype of Dog to showcase deeper inheritance)

public class Entity
{
    public int Id;
}

public class Animal : Entity
{

}

public class Dog : Animal
{

}

public class Dalmation : Dog
{

}

public class Vehicle : Entity
{

}

public class Submarine : Vehicle
{

}

public class Car : Vehicle
{

}

A simple registry to store them would look like

public class EntityRegistry
{
    private readonly Dictionary<int, Entity> _entities;


    public EntityRegistry()
    {
        _entities = new Dictionary<int, Entity>();
    }

    public void AddOrReplace(Entity entity) => _entities[entity.Id] = entity;
}

Getting by ID is easy since we're storing in a dictionary, just add

public Entity GetById(int id) => _entities[id];

To get a collection of entities based on a given type passed in, you can rely on GetType and IsSubclassOf.

public IReadOnlyList<Entity> GetByType(Type entityType)
{
    return _entities
        .Where(e => e.Value.GetType() == entityType ||
                    e.Value.GetType().IsSubclassOf(entityType))
        .Select(e => e.Value)
        .ToList();
}

This does iterate over everything in the dictionary per request, but that's pretty normal. The only other way would be to have a variety of additional collections and lots of if/else statements to figure out which one to return from. Not at all recommended.

Here's a simple example for a console app to showcase this

static void Main(string[] args)
{
    var cats = new List<Animal>
    {
        new Animal { Id = 1 },
        new Animal { Id = 2 },
    };
    var dogs = new List<Dog>
    {
        new Dog { Id = 3 },
        new Dog { Id = 4 },
    };
    var cars = new List<Car>
    {
        new Car { Id = 5 },
        new Car { Id = 6 },
    };
    var dalmations = new List<Dalmation>
    {
        new Dalmation { Id = 7 }
    };

    var registry = new EntityRegistry();
    foreach (var cat in cats)
    {
        registry.AddOrReplace(cat);
    }

    foreach (var dog in dogs)
    {
        registry.AddOrReplace(dog);
    }

    foreach (var car in cars)
    {
        registry.AddOrReplace(car);
    }

    foreach (var dalmation in dalmations)
    {
        registry.AddOrReplace(dalmation);
    }

    Console.WriteLine("Get entity #5");
    var carNumber5 = registry.GetById(5);
    Console.WriteLine(carNumber5.Id);

    Console.WriteLine();
    Console.WriteLine("Get all animals (IDs 1,2,3,4,7)");
    var allAnimals = registry.GetByType(typeof(Animal));
    foreach (var animal in allAnimals)
    {
        Console.WriteLine(animal.Id);
    }

    Console.WriteLine();
    Console.WriteLine("Get just dogs (IDs 3,4,7)");
    var justDogs = registry.GetByType(typeof(Dog));
    foreach (var dog in justDogs)
    {
        Console.WriteLine(dog.Id);
    }

    Console.WriteLine();
    Console.WriteLine("Get just dalmations (IDs 7)");
    var justDalmations = registry.GetByType(typeof(Dalmation));
    foreach (var dalmation in justDalmations)
    {
        Console.WriteLine(dalmation.Id);
    }
}
  • Related