C#:通过传递类型(从另一个静态对象检索的类型)进行通用工厂对象实例化

时间:2013-11-14 22:10:21

标签: c# generics types interface casting

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

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

为了实现所需,我创建了一个界面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.";
    }
}

请注意私有构造函数,它仅用于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表达式使(在我的机器上)单位创建更快,而军队可以变得非常大,快速通用创建正是我想要实现的目标。 < / p>

为了覆盖一切需要开始的战斗,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

}

现在,如果你已经达到了这个目标,你就会知道整个领域 - 它甚至可以编译,一切看起来都很明亮,直到......

直到现在:我创建了一个控制台应用程序,添加了对上述内容的引用,并尝试从内幕中获益。 为了完整描述我的困惑,我先注意正在工作

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

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

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. 有什么方法,我可以将类型传递给军营,而不必更改底层基础设施吗?
  2. 或者我的设计有什么问题吗?

  3. 过去两天我一直在谷歌上搜索,只有明确的方式是改变军营,这实际上是我不想要的。

    编辑第1期:重新思考概念和所有内容时:IBattleUnit首先被描述为每个单位都可以做的一系列核心战斗行动(我们想要就是这样)。我不想介绍基类,只是因为我知道,为了清楚可能有GroundUnitBaseFlyingUnitBase抽象类,我们希望有清晰和合理的设计......但绝对是必须只有一个静态Barracks

    仍然为BattleUnits - 现在把一个基类放在我的眼里似乎可以改变代码可以运行的东西,我正在尝试这样做...阅读,我写的内容让我想到{ {1}}类可能甚至不能帮助设计,但在某种程度上它的可编译性。所以这是我重新思考所写内容之后的第一个想法。

5 个答案:

答案 0 :(得分:1)

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)

答案 1 :(得分:1)

你可以使用反射这样做:

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

答案 2 :(得分:1)

如果您拥有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

答案 3 :(得分: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());
    }
}

答案 4 :(得分:0)

我的策略是创建一个Dictionary<Type, Barracks<IBattleUnit>>,假设您打算在尝试从中检索之前定义所有营房。这样你就可以通过钥匙进行匹配并安全地施放。

这需要军营&lt;&gt;不是一个静态的类。除非你有非常具体的理由,比如你正在管理的某种外部资源(甚至可以说是当时),你可能不需要静态类。

虽然为所有这些创建静态似乎可以使一切变得更容易,但最终您可能会创建对可能更改的资源的依赖性。如果你发明了另一种单位类型,你必须在军营中注册它,这与你不想创建基类的原因没有什么不同,如果你忘了你会抛出异常,甚至是<因为它违反了最低惊喜原则,所以更糟