0

I've different classes (e.g.: Player, Enemy, Asteroids...) - that are supposed to have one of different statuses (e.g.: ok, teleporting...).

enum Status
{
    Ok,
    Teleporting,
    ...
};

Using an interface, I'd like to force those classes to declare only the existance of a enum status variable inside them. However, I want no constraints on values. That is, a Player and an Enemy can have different values inside status (e.g.: one can teleport, the other one can't).

How can I do that?

Update1

public enum Status
{
    Ok,
    Teleporting
};

public interface IHaveStatus
{
    Status Status { get; set; }
}

Update 2

public enum Status
{
    Ok,
    Teleporting
};

public interface IHaveStatus
{
    Status status;
}
Giovarco
  • 63
  • 1
  • 11

4 Answers4

1

You could declare the interface as follows:

public interface IHaveStatus {
    Status Status { get; set; }
}

and let the specific classes implement that interface:

class Player: IHaveStatus {
    public Status Status { get; set; }
}

This will not limit the usage of the status field, it can have any value that is legal for the Status enum.

If (however) you would like to apply some logic (enemy can't teleport), than this may not be the right design.

If you want to specify that players have certain capabillities (i.e.: teleporting) you could use an attribute or an interface to identify those classes.

Using interfaces:

public interface ICanTeleport { }

public class Player : ICanTeleport { ... }

you can determine capabillities using a cast:

if(somePlayer is ICanTeleport) { .. }

or when using attributes:

[System.AttributeUsage(System.AttributeTargets.Class)]  
public class Teleporter : System.Attribute  
{ }

[Teleporter()]
public class Player { ... }

Then you will have to use reflection to determine the attributes, see this question.

  • Using interfaces makes the behavior bound to the class inheritance. It also gives you the abillity to add methods to the interface (i.e. a Teleport(coordinates) method).

  • Using attributes gives more possibillites to mix and match capabillities on the fly, as there is no class hierarchy forcing the capabillities.

oɔɯǝɹ
  • 7,219
  • 7
  • 58
  • 69
  • I'm not sure I would force a setter on the interface, but other then that, great answer! :-) – Zohar Peled Oct 29 '17 at 10:57
  • @ZoharPeled hey, it's just a property. do with it what you want -) – oɔɯǝɹ Oct 29 '17 at 11:07
  • It's just that both our answers was the same other then that detail when I wrote that comment... – Zohar Peled Oct 29 '17 at 11:10
  • @oɔɯǝɹ Thanks for the in-depth answer. So in the end the code will be like this: (See updated post: #Update1) And classes that implements the IHaveStatus interface will only be able to use **all or some** of the values exposed in `public enum Status` (in this case: ok and teleporting), but not add new one? Is this correct? By the way, while we discuss, I'll review my design choices :) – Giovarco Oct 29 '17 at 12:15
  • @oɔɯǝɹ and why can't I write #Update2 ? – Giovarco Oct 29 '17 at 12:38
  • @Giovarco if you extend the enum (add new values), all classes that implemented the interface / enum will directly be able to use these new values (ignoring technicallities such as compile times of diffrent assemblies etc..) – oɔɯǝɹ Oct 29 '17 at 13:51
1

Unless I'm missing something obvious, a simple interface like this will do it:

 public interface IHaveStatus
 {
      Status Status {get;}
 }

Values constraints I would put in the concrete classes. For instance, if a player can't teleport, a good place to constraint that would be in the property setter:

public class Player : IHaveStatus
{
    private Status _status;

    public Status Status 
    {
        get {return _status;}
        set
        {
            // a player can't teleport...
            if(value != Status.Teleport)
            {
                _status = value;
            }
        }
    }
}
Zohar Peled
  • 79,642
  • 10
  • 69
  • 121
1

You can't, at least not with the same enum type. You would have to declare different enums for every set of allowed values. Pro: you get compile time checking (as long as you explicitly assign values as is usually the case with Enums values). Con: You have to declare a new Enum for every allowable range.

Alternatively you check the incoming value at run time at the type level in the setter (method or property) and enforce any constraint there. Pro: you only need one enum for the complete range. Con: The downside of this solution is you could have bugs that you will not catch until runtime.

Solution enum per accepted range

enum EnemyStatus
{
    Ok
}

enum UserStatus
{
    Ok,
    Teleporting
}

Solution constraint at the type level

class Enemy : IStatusable {

  public Status CurrentStatus {
    get {return _status;}
    set {if(value == Status.Teleporting) throw new NotSupportedException();
         this._status = value;
    }}
}

interface IStatusable {
   Status CurrentStatus {get;set;}
}
Igor
  • 60,821
  • 10
  • 100
  • 175
  • The question was specifically about NOT limiting the possible values. I think you may have misread the question.. – oɔɯǝɹ Oct 29 '17 at 13:56
1

it is possible to combine multiple values in a single enum field if enum is marked with [Flags] attribute:

[Flags]
enum Status
{
    Ok = 0,
    Teleporting = 1,
    Flying = 2,
    Dancing = 4
}

then declare interface with one enum property:

interface Base
{
    Status Abilities { get; }
}

class Player : Base
{
    public Status Abilities { get; set; }
}

and test objects for different values like this:

var p = new Player();
p.Abilities = Status.Ok | Status.Teleporting;

bool canTeleport = p.Abilities.HasFlag(Status.Teleporting);
bool canFly = p.Abilities.HasFlag(Status.Flying);
ASh
  • 34,632
  • 9
  • 60
  • 82