Изменить значение словаря как выходной параметр и ссылку

Я хочу создать пример Entity-Component-System. У меня есть такие компоненты, как

internal struct Position : IComponent
{
    public int X { get; set; }
    public int Y { get; set; }
}

а также

internal struct Movementspeed : IComponent
{
    public float Value { get; set; }
}

которые реализуют

internal interface IComponent
{
}

Проходя по компонентам, я хочу найти их как можно быстрее. Я думал о создании словаря, который принимает тип компонентов в качестве ключа и компонент в качестве значения.

internal class ComponentCollection
{
    public ComponentCollection()
    {
        components = new Dictionary<Type, object>();
    }

    private Dictionary<Type, object> components;

    public void AddComponent<T>(T component) // add a new component
    {
        Type componentType = typeof(T);
        components.Add(componentType, component as IComponent);
    }

    public void RemoveComponent(Type componentType) // remove the component by type
    {
        components.Remove(componentType);
    }

    public bool TryGetComponent<T>(out T component) // get the specified component
    {
        try
        {
            Type componentType = typeof(T);
            component = (T)components[componentType];
            return true;
        }
        catch (Exception)
        {
            component = default(T);
            return false;
        }
    }
}

Есть две проблемы, которые возникают.

  1. Что если кто-то создаст новый MovementspeedComponent, используя float movementspeed и пытается это добавить? Наличие двух значений с плавающей точкой приведет к исключению дубликата ключа. Я мог бы только добавить пользовательские компоненты структуры, которые реализуют IComponent интерфейс для предотвращения дубликатов.

  2. Когда я пытаюсь изменить компоненты, они не изменены по ссылке

    private static void Main(string[] args)
    {
        ComponentCollection components = new ComponentCollection();
        components.AddComponent(new Position());
        components.AddComponent(new Movementspeed());
    
        if (components.TryGetComponent(out Position fooPosition))
        {
            fooPosition.X++;
            Console.WriteLine(fooPosition.X); // prints 1
        }
    
        if (components.TryGetComponent(out Position barPosition))
        {
            Console.WriteLine(barPosition.X); // prints 0
        }
    
        Console.ReadLine();
    }
    

Какие-либо предложения?

2 ответа

Решение

Что если кто-то создаст новый MovementspeedComponent, используя float движения peed

Типы хороши для ключей, когда у нас есть один объект / коллекция для каждого типа, в противном случае Dictionary<Type, object> не подходит, ключ должен быть уникальным.

Когда я пытаюсь изменить компоненты, они не изменены по ссылке

Поскольку все компоненты structС этой строкой кода:

components.TryGetComponent(out Position fooPosition)

Получает копию структуры, которую мы имеем в словаре. Цитата из руководства C#:

Структуры копируются по назначению. Когда структура присваивается новой переменной, все данные копируются, и любое изменение новой копии не изменяет данные для исходной копии. Это важно помнить при работе с коллекциями типов значений, таких как Словарь.

Что если кто-то создаст новый MovementspeedComponent, используя float движения peed, и попытается добавить его?

Я вижу 2 решения. Либо используйте классы вместо структур, либо добавьте метод Update для обновления структуры в словаре

public void UpdateComponent<T>(T component)
{
    Type componentType = typeof(T);
    components[componentType] = component;
}

Затем вызовите этот метод после обновления структуры:

if (components.TryGetComponent(out Position fooPosition))
{
    fooPosition.X++;
    components.UpdateComponent(fooPosition);
    Console.WriteLine(fooPosition.X); // prints 1
}

Если вы будете использовать классы вместо структур, ваше старое решение должно работать.

Другие вопросы по тегам