【问题标题】:C# : Generic factory object instantiation by passing type (type retrieved from another static object)C#:通过传递类型的通用工厂对象实例化(从另一个静态对象检索的类型)
【发布时间】:2013-11-28 03:52:10
【问题描述】:

我创建了一个小抽象域来说明我面临的问题,所以它就是这样。

有一个中世纪的游戏,玩家是他们军队的将军,整个战斗主要受战斗计划的影响,战斗计划是在战斗开始之前制定的,比如说准备模式。

为了实现所需要的,我创建了一个接口IBattleUnit 并使事情变得非常简单:

public interface IBattleUnit
{
    void Move();
    void Attack();
    string Salute();
}

目前使用三种类型的单元就可以了,所以Archer.csPikeman.csSwordsman.cs 以几乎相同的方式实现接口:

public class Swordsman : IBattleUnit
{
    private Swordsman() {}

    public void Move()
    {
        //swordsman moves
    }

    public void Attack()
    {
        //swordsman attacks
    }

    public string Salute()
    {
        return "Swordsman at your service, master.";
    }
}

注意 private 构造函数,它是用于仅在Barracks 招募战斗单位的,这是通用工厂

public static class Barracks<T> where T : class, IBattleUnit
{
    private static readonly Func<T> UnitTemplate = Expression.Lambda<Func<T>>(
       Expression.New(typeof(T)), null).Compile();

    public static T Recruit()
    {
        return UnitTemplate();
    }
}

注意:空构造函数的预编译 lambda 表达式使(在我的机器上)单元创建更快,虽然军队可以变得非常大,但快速泛型创建正是我想要实现的目标。

因为已经涵盖了战斗需要开始的所有内容,BattlePlan 解释是唯一缺少的部分,所以我们来了:

public static class BattlePlan
{
    private static List<Type> _battleUnitTypes;
    private static List<Type> _otherInterfaceImplementors;
    //...

    private static Dictionary<string, string> _battlePlanPreferences;

    private static Type _preferedBattleUnit;
    private static Type _preferedTransportationUnit;
    //...

    static BattlePlan()
    {
        //read the battle plan from file (or whereever the plan init data originate from)
        //explore assemblies for interface implementors of all kinds
        //and finally fill in all fields
        _preferedBattleUnit = typeof (Archer);
    }

    public static Type PreferedBattleUnit
    {
        get
        {
            return _preferedBattleUnit;
        }
    }

    //... and so on

}

现在,如果您达到了这一点,您就会了解整个域 - 它甚至可以编译并且一切看起来都很明亮,直到...

到目前为止:我创建了一个控制台应用程序,添加了对上述内容的引用,并尝试从引擎盖下的内容中获利。 为了完整描述我的困惑,我首先注意到 IS WORKING

  • 如果我想让兵营给我一个特定的 BattleUnit,我可以实例化它,让它战斗、移动和敬礼。如果以这种方式进行实例化:
IBattleUnit unit = Barracks<Pikeman>.Recruit();
  • 如果我想知道基于作战计划的首选单位是什么,我可以得到它,我可以询问它的AssemblyQualifiedName,我得到类型(实际上它是Archer,就像它留在BattlePlan) ,长话短说,当我打电话时,我得到了我所期望的:
Type preferedType = BattlePlan.PreferedBattleUnit;

在这里,当我希望 BattlePlan 为我提供一个 Type 而我只是将 Type 传递给 Barracks 以实例化某种单元时,VisualStudio2012(当前版本的更新器)阻止我并且不编译代码,而导致错误的代码是:

Type t = Type.GetType(BattlePlan.PreferedBattleUnit.AssemblyQualifiedName);
IBattleUnit u = Barracks<t>.Recruit();

无论我做什么,无论是传递t,还是传递为typeof(t),或者尝试将其转换为IRepository……我仍然无法编译这样的代码,错误列表中有(至少)两个错误:

Error   1   Cannot implicitly convert type 't' to 'BattleUnits.cs.IBattleUnit' Program.cs
Error   2   The type or namespace name 't' could not be found (are you missing a using directive or an assembly reference?) Program.cs

所以对于实际问题:

  1. 有什么方法可以将类型传递给 Barracks,而不必更改底层基础架构?
  2. 或者我有什么设计上的错误?

过去两天我一直在谷歌上搜索,但唯一明确的方法是改变兵营,这实际上是我不想要的。

第 1 号编辑:在重新思考这个概念和所有内容时:IBattleUnit 最初被描述为每个单位都能够执行的一组核心战斗行动(我们希望它能够做到)就这样)。我不想介绍基类,只是因为我知道,可能会有GroundUnitBaseFlyingUnitBase 抽象类,我们希望有清晰和合乎逻辑的设计......但绝对必须只有一个静态的Barracks

仍然适用于 BattleUnits - 现在将一个基类放在我的眼中似乎可以改变代码可运行的事情,我正在尝试这一点......阅读,我写的东西让我思考@ 987654345@ 类甚至可能对设计没有帮助,但在某种程度上它的可编译性。所以这是我重新思考所写内容后的第一个想法。

【问题讨论】:

  • 基本问题是泛型参数在编译时必须保持不变。但是,我可能会建议您将接口的性质更改为 ISalute、IAtack 和 IMove,从而允许具有您想要的特定功能的具体战士。这将使您远离您遇到的问题以及基类。

标签: c# generics types interface casting


【解决方案1】:

你真的不需要Barracks 是通用的。 此解决方案不使用反射,因此效率更高:

public static class Barracks
{
    private static readonly IDictionary<Type, Func<IBattleUnit>> FactoryMethods = new Dictionary<Type, Func<IBattleUnit>>();

    public static void Register<T>(Func<IBattleUnit> factory) where T : IBattleUnit
    {
        FactoryMethods.Add(typeof(T), factory);
    }

    public static IBattleUnit Recruit<T>() where T : IBattleUnit
    {
        return Recruit(typeof (T));
    }

    public static IBattleUnit Recruit(Type type)
    {
        Func<IBattleUnit> createBattleUnit;
        if (FactoryMethods.TryGetValue(type, out createBattleUnit))
        {
            return createBattleUnit();
        }

        throw new ArgumentException();
    }
}

public class Swordsman : IBattleUnit
{
    static Swordsman()
    {
        Barracks.Register<Swordsman>(() => new Swordsman());
    }
}

【讨论】:

  • 我花了一段时间才赶上你的帖子,我肯定正在尝试它。但我的问题是,如果 Swordsman 类不知道 Barracks 会发生什么:Barracks 驻留在不同的项目中并且 Swordsman 没有对 Barracks 的引用?
  • @jmodrak:这是一个很好的观点。这本质上是一个隐藏在静态类构造函数中的服务定位器,非常危险。
  • @ldalonzo:但正如我所见,可以在 Barracks 的静态构造函数中注册我想要注册的所有类型。我需要预编译的 lambda 构造函数,它们被证明在创建大量对象方面非常有效,这些对象最初是相同的(无参数构造函数),我正在评估你和 smartcaveman 的答案之间的 atm,生产将看到混合,并朝着性能。
  • 无论你把声明放在哪里,它都是一个隐藏的依赖。
  • @Alex Beisley:不一定“没关系”。兵营需要了解士兵的类型。但是为什么士兵需要知道,有兵营呢?所以我不会同意不管这个词。
【解决方案2】:
public static class Barracks
{
    public static IBattleUnit Recruit(Type preferredType)
    {
        return (IBattleUnit)typeof(Barracks<>).MakeGenericType(preferredType).GetMethod("Recruit", BindingFlags.Public|BindingFlags.Static).Invoke(null,null);
    }
}

然后调用

Barracks.Recruit(BattlePlan.PreferredBattleUnit)

【讨论】:

  • 谢谢,这完成了工作!如果 BattlePlan 更喜欢弓箭手,则实例化一个弓箭手,如果我们选择 Pikeman,则实例化一个 pikeman。尽管如此,它仍然需要很少的重新设计:或者具有您的 Recruit 方法的单独类将使用预编译的 lambdas 调用我的 Recruit 方法,或者我将我的 Barracks 类作为嵌套类放入您的类中。更让我担心的是,我正准备验证这种方法的性能效果,虽然从第一个角度来看,它们似乎不是太大的问题。
  • @jmodrak - 你听说过控制反转吗?有很多框架以更灵活的方式为你做这种事情。查找 StructureMap、Castle Windsor、Ninject 等。实际上有一个视频与您的领域模型惊人地相似:youtube.com/watch?v=dEu__OKeL6A
  • 我其实对 WindsorCastle 很熟悉,但由于某些原因,这里不适用 IoC
  • 我在创建 1000 万个实例时对这种方法进行了基准测试,性能差异简直是疯狂的;使用前一个时 - 这意味着没有 .MakeGeneric 和 .GetMethod 您可以在不到一秒的时间内完成它,而您的方法在我的机器上消耗大约 15 秒。这显然没有我希望的那么快,将继续调整。但是,我们热烈欢迎每一条额外的信息。
  • @jmodrak - 如果您正在评估性能结果,那么您应该将反射结果缓存在IDictionary&lt;Type,MethodInfo&gt; 中。您甚至可以更进一步构建委托,并拥有一个IDictionary&lt;Type,Func&lt;IBattleUnit&gt;&gt;。之后尝试对其进行基准标记。我回答的重点是教你如何调用产生错误的方法,而不是为你提供生产就绪代码。
【解决方案3】:

您可以使用反射来做到这一点,如下所示:

IBattleUnit unit = typeof(Barracks).GetMethod("Recruit").MakeGenericType(BattlePlan.PreferedBattleUnit).Invoke(null, null) as IBattleUnit;

【讨论】:

  • 这不适用于他的代码。 Barracks&lt;T&gt; 是泛型类型,Recruit 是非泛型方法
【解决方案4】:

如果您有PreferedBattleUnit 的实例,您只需使用dynamic 关键字。请看一下这个问题(John Skeet 的回答):(编辑:这可能不是很有帮助,因为您的方法不是通用的)

Pass concrete object type as parameter for generic method

如果您没有该对象的实例,请查看以下问题(同样,John Skeet 回答):

Generics in C#, using type of a variable as parameter

【讨论】:

  • 我没有实例,我只知道类型。但是链接非常好!
【解决方案5】:

我的策略是创建一个Dictionary&lt;Type, Barracks&lt;IBattleUnit&gt;&gt;,假设您打算在尝试从它们检索之前定义所有兵营。这样你就可以通过键匹配并安全地施放。

这将要求兵营不是静态类。除非您有非常具体的原因,例如您正在管理的某种外部资源(甚至可以说是这样),否则您可能不需要静态类。

虽然为所有这些创建静态数据似乎会使一切变得更容易,但最终您会创建对可能更改的资源的依赖关系。如果你发明了另一种单元类型,你必须将它注册到军营,这与你不想制作基类的原因没有任何不同,如果你忘记了你会抛出异常,甚至 更糟,因为它违反了最小意外原则。

【讨论】:

  • 我觉得这样不行,因为兵营需要是一个静态类。
  • @jmodrak:如果您打算将军营设为静态,那是对的,但如果您不需要,我会避免将其设为静态。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-28
  • 1970-01-01
  • 2021-10-27
  • 2011-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多