3

In my code have an interface damagable:

public interface Damagable
{
    public static float maxHealth { get; set; }
    public float currentHealth { get; set; }
}

Different classes implement damagable, where currentHelath is unique per instance. The idea was that different instances of a given type would all share the same maxHealth, to handle things like regeneration and damage based on maxHealth.

Would the static keyword here solve the problem?

I could just set maxHealth in the constructor for each class but I was hoping there would be a way to just make it static per class.

Is there a way to have an interface property to be different on every class that implements it, while each instance of that class has the same value, or is there a better way to do this?

Eventually I'll have multiple classes something like:

public class LargeObstacle : Damagable
{
    public float maxHealth { get; set; } = 100;
    public float currentHealth { get; set; }
}
public class SmallObstacle : Damagable
{
    public float maxHealth { get; set; } = 40;
    public float currentHealth { get; set; }
}

And a List<Damagable> that I can integrate through and get maxHealth without knowing the classes of each element.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • 2
    Are you using [C# 11](https://stackoverflow.com/a/60083436/1043380)? – gunr2171 Mar 16 '23 at 17:24
  • 2
    Before going too far on this, be aware that you're probably trying to implement a rules engine and it's extremely unlikely that C#'s inheritance rules will match your rules requirements. Eric Lippert's [Wizards and Warriors](https://ericlippert.com/2015/04/27/wizards-and-warriors-part-one/) – Damien_The_Unbeliever Mar 16 '23 at 17:47
  • 1
    Assuming multiple different classes are damageable, the maxHealth value *does* in fact vary per instance, since different instances of `Damagable` might be different classes. Making maxHealth static with respect to `Damagable` means that every class that implements `Damagable` will have the same maxHealth. – John Wu Mar 16 '23 at 18:01
  • Currently using C#9, but I'll look into updating. I'm not exactly trying ti implement a rules engine, but I'll take a look at Wizards and Warriors. I think I poorly explained my question. I'll edit a better explanation. – Dagan Hartmann Mar 16 '23 at 18:06
  • 1
    @Damien_The_Unbeliever what a fascinating read (again) from Eric Lippert. Thank you for sharing. – JAlex Mar 17 '23 at 13:27

1 Answers1

8

You could make it an instance property having a static backing field (respecting the C# naming conventions):

public interface IDamagable
{
    float MaxHealth { get; set; }
    float CurrentHealth { get; set; }
}

public class Damagable : IDamagable
{
    private static float _maxHealth;
    public float MaxHealth { get => _maxHealth; set => _maxHealth = value; }

    public float CurrentHealth { get; set; }
}

C# 11 introduced static virtual members in interfaces allowing to effectively declare static members that have to be implemented by the class:

public interface IDamagable
{
    static abstract float MaxHealth { get; set; }
    float CurrentHealth { get; set; }
}

public class Damagable : IDamagable
{
    public Damagable()
    {
        CurrentHealth = MaxHealth;
    }

    public static float MaxHealth { get; set; } = 100;
    public float CurrentHealth { get; set; }
}

Test:

var d1 = new Damagable();
Damagable.MaxHealth = 95;
var d2 = new Damagable();
var d3 = new Damagable();
Console.WriteLine(d1.CurrentHealth);
Console.WriteLine(d2.CurrentHealth);
Console.WriteLine(d3.CurrentHealth);

prints:

100
95
95

As you can see, you must access MaxHealth through the type name Damagable, since it is not bound to an instance.


If MaxHealth is a constant value, then you can also declare a constant. Constants are static by their nature.

public const float MaxHealth = 40;
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • Went and did some testing. Using a static backing field worked perfectly. I can set it per class and access it through the interface's maxHealth property without ever referencing the individual class. Thanks! – Dagan Hartmann Mar 16 '23 at 18:25
  • One question about backing fields, when doing something like `var d1 = new Damagable();` `Console.WriteLine(d1.MaxHealth)` Does it just return the `private static _maxHealth` field or does it copy it to `d1.MaxHealth`, storing it in memory once per instance? – Dagan Hartmann Mar 16 '23 at 18:36
  • 1
    I used [Expression-bodied members](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members) equivalent to `public float MaxHealth { get { return _maxHealth; } set { _maxHealth = value; } }`. As you can see, it returns resp. assigns `_maxHealth` directly and does not use additional memory. `get` and `set` are simply two methods and it's up to you what you are doing there. – Olivier Jacot-Descombes Mar 17 '23 at 13:21