我创建了一个抽象的小域来说明我面临的问题,所以就这样。
有一个中世纪的游戏,玩家是他们军队的将军,整个战斗主要受到战斗计划的影响,这是在战斗开始之前制定的,在准备模式下。
为了实现所需的目标,我创建了一个界面IBattleUnit
,并使事情变得非常简单:
public interface IBattleUnit
{
void Move();
void Attack();
string Salute();
}
现在有三种类型的单元就可以完成这项工作,所以Archer.cs
、Pikeman.cs
和Swordsman.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 表达式使(在我的机器上)单位创建更快,而军队可以获得非常大,但快速创建泛型正是我想要实现的。
因为已经涵盖了战斗需要开始的所有内容,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
}
现在,如果您已经达到这一点,您就会知道整个域 - 它甚至可以编译并且一切看起来都很明亮,直到......
到目前为止:我创建一个控制台应用程序,添加对上述内容的引用,并尝试从引擎盖下的内容中获利。为了完整描述我的困惑,我首先注意什么是有效的:
- 如果我想让军营给我一个特定的战斗单位,我可以实例化它,让它战斗、移动和敬礼。如果实例化以这种方式完成:
IBattleUnit unit = Barracks<Pikeman>.Recruit();
- 如果我想知道基于作战计划的首选单位是什么,我可以得到它,我可以询问它的
AssemblyQualifiedName
,我得到类型(实际上是Archer
,就像它留在BattlePlan
一样),长话短说,当我打电话时,我得到了我所期望的:
Type preferedType = BattlePlan.PreferedBattleUnit;
在这里,当我期望战斗计划为我提供一个类型,而我只是将类型传递给军营以实例化某种单位时,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:重新思考概念和一切:IBattleUnit
最初被描述为每个单位都能做的一组核心战斗行动(我们希望它是这样)。我不想引入基类,只是因为我知道,为了缘故,可能会有GroundUnitBase
和FlyingUnitBase
抽象类,我们希望有清晰和合乎逻辑的设计......但绝对必须只有一个静态Barracks
.
仍然对于战斗单位 - 现在在我的眼中放置一个基类似乎可以改变代码可运行的内容,我正在尝试它......阅读,我写的东西让我想到UnitBase
类可能甚至对设计有所帮助,但在某种程度上对它的可编译性有所帮助。所以这是我重新思考所写内容后脑海中的第一个想法。
你真的不需要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());
}
}
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)
您可以使用反射来执行此操作,如下所示:
IBattleUnit unit = typeof(Barracks).GetMethod("Recruit").MakeGenericType(BattlePlan.PreferedBattleUnit).Invoke(null, null) as IBattleUnit;
如果您有PreferedBattleUnit
的实例,则只需使用 dynamic
关键字。请看一下这个问题(John Skeet的回答):(编辑:这可能不是很有帮助,因为你的方法不是通用的)
将具体对象类型作为泛型方法的参数传递
如果您没有对象的实例,请查看以下问题(再次,John Skeet 回答):
C# 中的泛型,使用变量类型作为参数
我的策略是创建一个Dictionary<Type, Barracks<IBattleUnit>>
,假设你打算在尝试从它们检索之前定义所有的军营。这样您就可以按键匹配并安全地投射。
这将要求军营<>不是一个静态的职业。 除非你有非常具体的原因,比如你正在管理的某种外部资源(甚至可以说),否则你可能不需要静态类。
虽然似乎为所有这些创建静态数据会使一切变得更容易,但最终您会创建对可能更改的资源的依赖关系。如果你发明了另一种单位类型,你必须在军营注册它,这与你不想制作基类的原因没有什么不同,如果你忘记了,你会抛出异常,这更糟糕,因为它违反了最小惊喜原则。