C# 从持有接口的集合返回类而不是接口

C# 从持有接口的集合返回类而不是接口,c#,entity-component-system,C#,Entity Component System,我想创建一个小实体组件系统示例,并创建一些组件,如 internal struct Position : IComponent { public int X { get; set; } public int Y { get; set; } } 及 每个组件都实现一个当前为空的接口IComponent。当系统在实体中循环时,我希望快速找到相关组件 我考虑创建一个字典,将组件类型作为键,将当前实体的组件作为值 我从公共字典组件{get;} 我可以使用myEntity.compone

我想创建一个小实体组件系统示例,并创建一些组件,如

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

每个组件都实现一个当前为空的接口
IComponent
。当系统在实体中循环时,我希望快速找到相关组件

我考虑创建一个字典,将组件类型作为键,将当前实体的组件作为值

我从
公共字典组件{get;}

我可以使用myEntity.components.add(typeof(Movement),newmovement()作为IComponent)添加组件

但是如何返回组件呢?我创建了一个运动系统示例

internal class Movement : ISystem
{
    public void Update()
    {
        foreach (Entity entity in EntityPool.activeEntities.Values) // Loop through all entities
        {
            Dictionary<Type, IComponent> components = entity.Components;

            if (components.TryGetValue(typeof(Position), out Position positionComponent))
            {
                if (components.TryGetValue(typeof(MovementSpeed), out MovementSpeed movementSpeedComponent))
                {
                    // TEST: move (1 * movementspeed) units
                    positionComponent.X += movementSpeedComponent.Value;
                    positionComponent.Y += movementSpeedComponent.Value;
                }
            }
        }
    }
}
内部类移动:ISystem
{
公共无效更新()
{
foreach(EntityPool.activeEntities.Values中的实体)//遍历所有实体
{
字典组件=实体组件;
if(组件TryGetValue(类型(位置),输出位置组件))
{
if(components.TryGetValue(typeof(MovementSpeed)、out MovementSpeed movementSpeedComponent))
{
//测试:移动(1*movementspeed)单位
positionComponent.X+=movementSpeedComponent.Value;
positionComponent.Y+=movementSpeedComponent.Value;
}
}
}
}
}
如果(components.TryGetValue(typeof(Position),out Position positionComponent))
由于字典的值本身不返回所需类型的组件而崩溃,它将返回接口

我怎样才能让它工作


(是的,我知道我可以使用ECS框架,但为了学习,我想自己做)

简单的回答:你不能。如果dictionary的类型为
dictionary
,则它将仅返回
IComponent

但是,您可以为此创建扩展方法:

public static class DictionaryExtensions
{
    public static TComponent GetValueOrNull<TComponent>(this Dictionary<Type, IComponent> dict) where TComponent : IComponent
    {
        dict.TryGetValue(typeof(TComponent), out IComponent component);
        return component as TComponent;
    } 
}
公共静态类字典扩展
{
公共静态TComponent GetValueOrNull(此字典目录),其中TComponent:IComponent
{
dict.TryGetValue(类型为(t组件),输出i组件);
返回组件作为TComponent;
} 
}
并使用它:

internal class Movement : ISystem
{
    public void Update()
    {
        foreach (Entity entity in EntityPool.activeEntities.Values) // Loop through all entities
        {
            var posComponent = entity.Components.GetValueOrNull<Position>();

            if (posComponent != null)
            {
                // some code
            }
        }
    }
}
内部类移动:ISystem
{
公共无效更新()
{
foreach(EntityPool.activeEntities.Values中的实体)//遍历所有实体
{
var posComponent=entity.Components.GetValueOrNull();
if(posComponent!=null)
{
//一些代码
}
}
}
}

如果插入IComponent类型的项,则只能将该项作为IComponent检索。 如果向字典询问特定类型,可以直接转换为该类型

        foreach (Entity entity in EntityPool) // Loop through all entities
        {
            Dictionary<Type, IComponent> components = entity.Components;
            if (components.TryGetValue(typeof(Position), out IComponent positionComponent))
            {
                Position position = (Position)positionComponent;
                if (components.TryGetValue(typeof(MovementSpeed), out IComponent movementSpeedComponent))
                {
                    MovementSpeed speed = (MovementSpeed)movementSpeedComponent;
                    // TEST: move (1 * movementspeed) units
                    position.X += speed.Value;
                    position.Y += speed.Value;
                }
            }
        }
foreach(EntityPool中的实体)//遍历所有实体
{
字典组件=实体组件;
if(组件.TryGetValue(类型(位置),输出IComponent位置组件))
{
位置位置=(位置)位置组件;
if(components.TryGetValue(typeof(MovementSpeed),out-IComponent-movementSpeedComponent))
{
MovementSpeed速度=(MovementSpeed)MovementSpeed组件;
//测试:移动(1*movementspeed)单位
位置X+=速度值;
位置Y+=速度值;
}
}
}
使用linq,您有一种非常有效的方法,可以对列表进行操作。也许这对你来说是更好的方式。以下是一个例子:

    public void Update2()
    {
        List<IComponent> list = new List<IComponent>();
        list.OfType<Position>().ToList().ForEach(p =>
        {
            var speed = list.OfType<MovementSpeed?>().FirstOrDefault();
            if (speed.HasValue)
            {
                p.X = speed.Value.Value;
                p.Y = speed.Value.Value;
            }
        });
    }
public void Update2()
{
列表=新列表();
list.OfType().ToList().ForEach(p=>
{
var speed=list.OfType().FirstOrDefault();
if(速度值)
{
p、 X=速度.Value.Value;
p、 Y=速度.Value.Value;
}
});
}

您需要使用反射来查找并获取指定成员的值。更好的方法是将成员放在界面中。我不确定我是否明白你在做什么,但这就是使用接口的目的。为什么你要嵌套
if
s?@JonathanWood你介意再解释一点吗?@Fabjan因为这个系统应该只更新附加了位置和移动速度组件的实体,并从字典中将其作为
i组件
,然后将其投射到
位置
。因为你只拉
位置
,演员阵容应该总是成功的(尽管我会使用
is
as
来确保并防止出现
无效的卡斯特例外情况
),这不是一个坏答案,但要知道,TryGet有一个更普遍的惯例。。。。方法。它们倾向于返回bool(如果得到则为true,如果没有则为false),并将实际值作为out参数返回。(参见字典中的一个例子,但即使是int.TryParse()这样的各种东西也基本上是相同的约定)。是的,我将名称改为GetValueOrnull我知道很难得到结果,但他真的不可能吗?这种方法怎么样?尤其是动态关键字。该解决方案的缺点是运行时的性能较慢,并且对对象的操作有很大的限制。
    public void Update2()
    {
        List<IComponent> list = new List<IComponent>();
        list.OfType<Position>().ToList().ForEach(p =>
        {
            var speed = list.OfType<MovementSpeed?>().FirstOrDefault();
            if (speed.HasValue)
            {
                p.X = speed.Value.Value;
                p.Y = speed.Value.Value;
            }
        });
    }