I'm making a game where each Actor is represented by a GameObjectController. Game Objects that can partake in combat implement ICombatant. How can I specify that arguments to a combat function must inherit from GameObjectController and implement ICombatant? Or does this indicate that my code is structured poorly?

public void ComputeAttackUpdate(ICombatant attacker, AttackType attackType, ICombatant victim)

In the above code, I want attacker and victim to inherit from GameObjectController and implement ICombatant. Is this syntactically possible?


Presumably all ICombatants must also be GameObjectControllers? If so, you might want to make a new interface IGameObjectController and then declare:

interface IGameObjectController
    // Interface here.

interface ICombatant : IGameObjectController
    // Interface for combat stuff here.

class GameObjectController : IGameObjectController
    // Implementation here.

class FooActor : GameObjectController, ICombatant
    // Implementation for fighting here.   
  • This is exactly what I want, except GameObjectController is a class, not an interface. Is there any way to do this with a class, or must I restructure it into an interface? – Nick Heiner Mar 28 '10 at 22:11
  • 2
    @Rosarch: You need to restructure. It's a well-known refactoring operation called 'Extract Interface' and can be performed automatically. You can also do it by hand very easily. msdn.microsoft.com/en-us/library/fb3dyx26.aspx – Mark Byers Mar 28 '10 at 22:25

I'd say it probably indicates you could restructure somehow, like, have a base Combatant class that attacker and victim inherit from, which inherits from GameObjectController and implements ICombatant.

however, you could do something like

ComputeAttackUpdate<T,U>(T attacker, AttackType attackType, U victim)
      where T: ICombatant, GameObjectController
      where U: ICombatant, GameObjectController

Although I probably wouldn't.


It is only syntactically possible if GameObjectController itself implements ICombatant; otherwise, I would say you have a design problem.

Interfaces are intended to define the operations available on some object; base classes identify what that object is. You can only pick one or the other. If accepting the ICombatant interface as an argument is not sufficient, it might indicate that ICombatant is defined too narrowly (i.e. doesn't support everything you need it to do).

I'd have to see the specifics of what you're trying to do with this object in order to go into much more depth.

What if you did this instead:

public class GameObjectControllerCombatant : GameObjectController, ICombatant
    // ...

Then derive your combatant classes from this instead of directly from GameObjectController. It still feels to me like it's breaking encapsulation, and the awkwardness of the name is a strong indication that your combatant classes are violating the Single Responsibility Principle... but it would work.

  • I could just make ICombatant have a getter for a GameObjectController, but that seems like unnecessary bloat. The code would always be public GameObjectController Controller {get {return this;} } – Nick Heiner Mar 28 '10 at 22:07
  • @Rosarch: If it's actually possible for the ICombatant interface to implement such a property then it would seem to indicate that GameObjectController does in fact already implement ICombatant (or could be made to); therefore, just accept a GameObjectController argument, you don't need the ICombatant. – Aaronaught Mar 28 '10 at 22:09
  • Thinking this through further, your predicament may indicate the opposite of this; that the classes that implement ICombatant have way too many responsibilities. It doesn't seem to follow logically that an ICombatant is actually the same instance as a "controller." I'd really like to see the code that you would hypothetically write in the method body if this were possible to do in the signature. – Aaronaught Mar 28 '10 at 22:14

Well, sort of. You can write a generic method:

public void ComputeAttackUpdate<T>(T attacker, AttackType type, T victim)
    where T : GameObjectController, ICombatant

That means T has to satisfy both the constraints you need. It's pretty grim though - and if the attacker and victim could be different (somewhat unrelated) types, you'd have to make it generic in two type parameters instead.

However, I would personally try to go for a more natural solution. This isn't a situation I find myself in, certainly. If you need to regard an argument in two different ways, perhaps you actually want two different methods?

  • Actually, the whole point of generic methods is that you have all the shared logic in one method. For instance, I have this method that does an HTTPRequest and turns the response into an object of the same type as <T> using XMLSerializer((typeof(T)).Deserialize(response). It's much easier to maintain than having ten slightly different code blocks doing essentially the same thing. – Captain Kenpachi Feb 25 '13 at 11:16
  • @JuannStrauss: Well yes - but that's my point. If you're genuinely doing the same thing, it's fine to have it as a generic method. But if you want to handle the values differently, it makes sense to have different overloads. – Jon Skeet Feb 25 '13 at 12:39

If you control all the classes in question, and if GameObjectController doesn't define any fields, the cleanest approach would be to define an IGameObjectController (whose properties and methods match those of GameObjectController) and an ICombatantGameObjectContoller (which derives from both IGameObjectController and ICombatant). Every class which is to be usable in situations that require both interfaces must be explicitly declared as implementing ICombatantGameObjectController, even though adding that declaration wouldn't require adding any extra code. If one does that, one can use parameters, fields, and variables of type ICombatantGameObjectController without difficulty.

If you can't set up your classes and interfaces as described above, the approach offered by Jon Skeet is a generally good one, but with a nasty caveat: to call a generic function like Mr. Skeet's ComputeAttackUpdate, the compiler has to be able to determine a single type which it knows is compatible with the type of the object being passed in and with all of the constraints. If there are descendants of GameObjectController which implement ICombatant but do not derive from a common base type which also implements GameObjectController, it may be difficult to store such objects in a field and later pass them to generic routines. There is a way, and if you need to I can explain it, but it's a bit tricky.

Your Answer

