Reputation: 229
I created a little abstract domain to illustrate the problem I am facing, so there it is.
There is a medieval game, where the players are generals of their army and the entire battle is mostly affected by the battle plan, which is made before the battle begins, in let's say preparation mode.
To achieve what's needed, I created an interface IBattleUnit
and kept things pretty simple:
public interface IBattleUnit
{
void Move();
void Attack();
string Salute();
}
Having three types of units will do the job for now, so Archer.cs
, Pikeman.cs
and Swordsman.cs
implement the interface in pretty much the same way:
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.";
}
}
Note the private constructor, it is intended for battle units to be recruited only in Barracks
, this is the generic factory
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();
}
}
Note: precompiled lambda expressions for the empty constructor make (on my machine) unit creation faster, and whereas the army can get really big, fast generic creation is exactly what I want to achieve.
For having covered everything a battle needs to be started, the BattlePlan
explanation is the only missing part, so here we come:
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
}
Now if you have reached this, you are aware of the whole domain - it even compiles and everything looks bright, until...
Until now: I create a console application, add references to the above mentioned, and try to profit from what's under the hood. For complete description of my confusion, I note what IS WORKING first:
IBattleUnit unit = Barracks<Pikeman>.Recruit();
AssemblyQualifiedName
, I get the Type (in fact it is Archer
, just as it stays in BattlePlan
) , long story short, I get what I expect to, when I call:Type preferedType = BattlePlan.PreferedBattleUnit;
And here, when I expect the BattlePlan to supply me with a Type and me just passing the Type to Barracks in order to instantiate some kind of Unit, VisualStudio2012 (resharper of current version) stops me and does not compile the code, while the code, that leads to the error is:
Type t = Type.GetType(BattlePlan.PreferedBattleUnit.AssemblyQualifiedName);
IBattleUnit u = Barracks<t>.Recruit();
No matter what I do, no matter whether I pass the t
, or pass it as typeof(t)
, or try converting it to IRepository
... I still end up not being able to compile such code, with (at least) two errors in the error list:
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
So to the actual questions:
I have spent the last two days googling around and still, with the only clear way being changing the Barracks, which in fact is what I would not want to.
EDIT no.1: When re-thinking the concept and everything : IBattleUnit
was first described as a set of core battle actions every Unit will be able to do (and we want it to be this way). I did not want to introduce base classes, just because I knew, there could possibly be GroundUnitBase
and FlyingUnitBase
abstract classes for the sake, we would like to have clear and logical design... But there absolutely has to be only one static Barracks
.
Still for the BattleUnits - putting one base class in my eyes now seems could change the things for code being runnable and I'm right on my way of trying that out ... reading, what I wrote made me think about UnitBase
class could possibly help not even the design but in some way its compilability. So this is the first idea in my mind after rethinking what's written.
Upvotes: 4
Views: 1633
Reputation: 1312
My strategy would be to create a Dictionary<Type, Barracks<IBattleUnit>>
, assuming you intend to have all the barracks defined before you try to retrieve from them. That way you can match by the key and cast safely.
This would require the Barracks<> to not be a static class. Unless you have very specific reasons like some kind of external resource you're managing (and arguably even then), you probably have no need for a static class.
While it may seem like creating statics for all of these will make everything easier, ultimately you create a dependency on a resource that may change. If you invent another unit type, you have to register it with the barracks, which is in no real way different than the reason you don't want to make base classes, and if you forget you'll throw exceptions, which is even worse, because it violates the Principle of Least Surprise.
Upvotes: 0
Reputation: 2225
You don't really need Barracks
to be generic.
This solution doesn't use reflection so it's much more efficient:
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());
}
}
Upvotes: 1
Reputation: 7005
If you have an instance of the PreferedBattleUnit
you simply need to use the dynamic
keyword. Please have a look at this question (John Skeet answer): (EDIT: This might not be very helpful as your method is not generic)
Pass concrete object type as parameter for generic method
If you don't have an instance of the object than have a look at the following question (again, John Skeet answer):
Generics in C#, using type of a variable as parameter
Upvotes: 1
Reputation: 10014
You can do this using reflection, something like this:
IBattleUnit unit = typeof(Barracks).GetMethod("Recruit").MakeGenericType(BattlePlan.PreferedBattleUnit).Invoke(null, null) as IBattleUnit;
Upvotes: 1
Reputation: 42246
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);
}
}
then call
Barracks.Recruit(BattlePlan.PreferredBattleUnit)
Upvotes: 1