通过一种方法实例化各种继承的类,而不进行反射

时间:2014-12-26 22:19:21

标签: c# reflection polymorphism abstract-class

在我正在进行的项目中,我有一组构成基于3D体素的环境(如Minecraft)。这些世界存储在外部数据文件中。

此文件包含以下数据: 每个街区, 它的位置, 和它的类型。

当调用LoadLevel方法时,我希望它迭代文件中每个块的数据,为每个块创建一个新的Block对象实例。传递像位置这样的东西没问题。它就像

一样简单
CreateBlock(Vector3 position)

问题在于类型。所有类型都是子类(想想抽象块,然后像GrassBlock或WaterBlock那样继承抽象Block的属性。)假设有一个像#34; GrassBlock"我想要创建,而不是通用块,我如何通过该方法实现这一点?我所知道的唯一方法是通过反思,我被建议远离。我可以通过泛型打字或其他方式来做到这一点吗?

这在游戏设计中似乎是一个非常重要的问题,但我所问过的任何人似乎都没有任何想法。有什么帮助吗?

4 个答案:

答案 0 :(得分:2)

通用打字仍需要反思。

首先:您正在寻找的是工厂模式。它为您创建对象,而无需在任何地方自己明确地执行此操作。

基本上有两种选择:

  • <强>反射

这确实会对其产生影响,但如果您尚未确定它是一个问题,请不要忽视它。它将是可读和可维护的。

  • <强>开关

对每个选项进行硬编码,并根据您传入的某种元数据创建新实例(这将标识每个块的类型)。这样做的好处是不使用反射,因此不会导致性能下降,但也会降低可扩展性,如果你有500个不同的块,你可以猜出代码的样子。

答案 1 :(得分:1)

如果没有反射,您可以使用switch的工厂方法。假设BlockType是一个枚举。

public static Block CreateBlock(BlockType type, Vector3 position)
{
    switch (BlockType type)
    {
    case BlockType.Grass:
        return new GrassBlock(position);
    case BlockType.Water:
        return new WaterBlock(position);
    default:
        throw new InvalidOperationException();
    }
}

但是为了拥有更可维护的东西,你仍然可以使用反射,直到它被证明是一个瓶颈。在这种情况下,您可以切换到运行时代码生成

private static readonly Dictionary<Type, Func<Vector3, Block>> _activators = new Dictionary<Type, Func<Vector3, Block>>();

public static Block CreateBlock(Type blockType, Vector3 position)
{
    Func<Vector3, Block> factory;

    if (!_activators.TryGetValue(blockType, out factory))
    {
        if (!typeof(Block).IsAssignableFrom(blockType))
            throw new ArgumentException();

        var posParam = Expression.Parameter(typeof(Vector3));
        factory = Expression.Lambda<Func<Vector3, Block>>(
            Expression.New(
                blockType.GetConstructor(new[] { typeof(Vector3) }),
                new[] { posParam }
            ),
            posParam
        ).Compile();

        _activators.Add(blockType, factory);
    }

    return factory(position);
}

此代码将在运行时生成工厂函数,这是第一次请求给定类型的块。如果需要,您可以使用ConcurrentDictionary来使此功能成为线程安全的。

可能对你的目的来说有点矫枉过正;)

答案 2 :(得分:1)

当然,您可以创建没有任何反射的对象。 简单地为每个类分配整数索引:

Func<Vector3, Block>[] factories =
{
    (v) => new GrassBlock(v),    // index 0
    (v) => new WaterBlock(v),    // index 1
    . . .
}

将此索引保存在外部数据中。在反序列化时读取Vector3 v和索引i,然后调用var block = factories[i](v);

答案 3 :(得分:0)

你为什么要避免反思?如果你只能在启动时执行此代码(听起来你可以在阅读文件时这样做),那么我个人在使用反射方面没有太大的问题。

另一种方法是存储完全限定的类型名称(例如My.System.Blocks.GrassBlock)并使用

加载该类型
var typeName = readStringTypeFromFile(file);
Block type = Activator.CreateInstance(typeName, location);

正如我所说,在启动时运行这样的东西对我很好,如果需要你可以测试它的性能。

快速而肮脏的小提琴:https://dotnetfiddle.net/BDmlyi