How can I make an “abstract” enum in a .NET class library?

百般思念 提交于 2019-11-29 02:48:46

问题


I'm making a server library in which the packet association is done by enum.

public enum ServerOperationCode : byte
{
    LoginResponse = 0x00,
    SelectionResponse = 0x01,
    BlahBlahResponse = 0x02
}

public enum ClientOperationCode : byte
{
    LoginRequest = 0x00,
    SelectionRequest = 0x01,
    BlahBlahRequest = 0x02
}

That works fine when you're working in your own project - you can compare which enum member is returned (i.e. if (packet.OperationCode == ClientOperationCode.LoginRequest)). However, since this is a class library, the user will have to define its own enum.

Therefore, I have two enums to add as "abstract" - ServerOperationCode and ClientOperationCode. I know it's not possible to implement abstract enums in C#. How would I go doing this?


回答1:


I like to use static instances on my classes when I need to do this. It allows you to have some default values but also lets it be extensible through the usual means of inheritance and interface implementations:

    public abstract class OperationCode
    {
        public byte Code { get; private set; }
        public OperationCode(byte code)
        {
            Code = code;
        }
    }

    public class ServerOperationCode : OperationCode
    {
        public static ServerOperationCode LoginResponse = new ServerOperationCode(0x00);
        public static ServerOperationCode SelectionResponse = new ServerOperationCode(0x01);
        public static ServerOperationCode BlahBlahResponse = new ServerOperationCode(0x02);

        public ServerOperationCode(byte code) : base(code) { }
    }

    public class ClientOperationCode : OperationCode
    {
        public static ClientOperationCode LoginRequest = new ClientOperationCode(0x00);
        public static ClientOperationCode SelectionRequest = new ClientOperationCode(0x01);
        public static ClientOperationCode BlahBlahRequest = new ClientOperationCode(0x02);

        public ClientOperationCode(byte code) : base(code) { }
    }

assuming packet.OperationCode return a byte, you will likely have to implement an == operator for byte. put this code into your abstract OperationCode class.

public static bool operator ==(OperationCode a, OperationCode b)
{
  return a.Code == b.Code;
}

public static bool operator !=(OperationCode a, OperationCode b)
{
  return !(a == b);
}

this will allow you to have the same check as you showed:

if (packet.OperationCode == ClientOperationCode.LoginRequest)



回答2:


Why does everyone think that Enums cannot be abstracted?

The class System.Enum IS the abstraction of an enumeration.

You can assign any enumeration value to an Enum, and you can cast it back to the original enumeration or use the name or value.

eg:

This little snippet of code is from a dynamic property collection used in one of my control libraries. I allow properties to be created and accessed through an enumeration value to make it slightly faster, and less human-error

    /// <summary>
    /// creates a new trigger property.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    /// <returns></returns>
    protected virtual TriggerProperty<T> Create<T>(T value, Enum name)
    {
        var pt = new TriggerProperty<T>(value, OnPropertyChanged, Enum.GetName(name.GetType(), name));
        _properties[name.GetHashCode()] = pt;
        return pt;
    }

I use Enum.GetName(Type, object) to get the name of the enumeration value (to supply a name for the property), and for speed and consistency reasons I use GetHashCode() to return the integer value of the enumeration member (the hash code for an int is always just the int value)

This is an example of the method being called:

    public enum Props
    {
        A, B, C, Color, Type, Region, Centre, Angle
    }

    public SpecularProperties()
        :base("SpecularProperties", null)
    {
        Create<double>(1, Props.A);
        Create<double>(1, Props.B);
        Create<double>(1, Props.C);
        Create<Color>(Color.Gray, Props.Color);
        Create<GradientType>(GradientType.Linear, Props.Type);
        Create<RectangleF>(RectangleF.Empty, Props.Region);
        Create<PointF>(PointF.Empty, Props.Centre);
        Create<float>(0f, Props.Angle);
    }



回答3:


If you mean to say that you want an enum that can be extended by clients of the library, check out my CodeProject article on the topic, Symbols as extensible enums.

Note that in my library, Symbol chooses ID numbers for the "enum values" automatically, since it is designed for use inside a single program rather than for exchanging values on a network. Perhaps it would be possible, however, to alter Symbol.cs to your liking so that clients can assign constant values to symbols.




回答4:


  1. Create an Enum for LoginResponse, SelectionResponse, etc., but don't specify the values.

  2. Have ServerOperationCode and ClientOperationCode implement a function that, given an integer bytecode, returns the appropriate value from your Enum.

Example:

public enum OperationCode
{
 LoginResponse,
 SelectionResponse,
 BlahBlahResponse
}

public interface IOperationCodeTranslator {
 public OperationCode GetOperationCode(byte inputcode);
 }

public class ServerOperationCode : IOperationCodeTranslator
{
  public OperationCode GetOperationCode(byte inputcode) {
    switch(inputcode) {
       case 0x00: return OperationCode.LoginResponse;
      [...]
    } 
}

Caveat: since interfaces can't define static functions, ServerOperationCode and ClientOperationCode would only be able to implement a common interface if said function is an instance function. If they don't need to implement a common interface, GetOperationCode can be a static function.

(Apologies for any C# snafus, it's not my first language...)




回答5:


If there is a database that is shared between your client and server application, then look-up tables may help; the table structure just contain an integer value (ID) and a string (the name), this table can be filled out by either side of your application (the client or the server) and read by the other. You can cache these table (in your code) in a dictionary for quick look-up.

You can also implement the same thing in the app.config file; force the user of your library to set these values in the app.config file which your library can access easily.




回答6:


I wrote a message switching library with a similar scenario a while back, and I decided to use generics to pass the user-defined enum. The main problem with this is you can't constrain your generic to only enum types, but can only say while T: struct. Someone may instantiate your type with some other primitive type (although, using ints could still be functional, provided they're all unique values. The dictionary will throw an exception if they're not. You could possibly add some additional check using reflection to ensure you pass an enum.

public abstract class DefaultMessageHandler<T> : IMessageHandler<T> where T : struct {
    public delegate void MessageHandlerDelegate(IMessage<T> message, IConnection connnection);

    private readonly IDictionary<T, MessageHandlerDelegate> messageHandlerDictionary = 
        new Dictionary<T, MessageHandlerDelegate>();

    protected void RegisterMessageHandler(T messageType, MessageHandlerDelegate handler) {
        if (this.messageHandlerDictionary.ContainsKey(messageType)) 
            return;
        else this.messageHandlerDictionary.Add(messageType, handler);
    }

    protected void UnregisterMessageHandler(T messageType) {
        if (this.messageHandlerDictionary.ContainsKey(messageType))
            this.messageHandlerDictionary.Remove(messageType);
    }

    protected virtual void HandleUnregisteredMessage(IMessage<T> message, IConnection connection) {
    }

    void IMessageHandler<T>.HandleMessage(IMessage<T> message, IConnection connection) {
        if (this.messageHandlerDictionary.ContainsKey(message.MessageType))
            this.messageHandlerDictionary[message.MessageType].Invoke(message, connection);
        else HandleUnregisteredMessage(message, connection);
    }
}

Given your example scenario, you'd just subclass it like this.

public sealed class ServerOperationHandler : DefaultMessageHandler<ServerOperationCode> {
    public ServerOperationHandler() {
        this.RegisterMessageHandler(ServerOperationCode.LoginResponse, this.HandleLoginResponse);
        this.RegisterMessageHandler(ServerOperationCode.SelectionResponse, this.HandleSelectionResponse);
    }

    private void HandleLoginResponse(IMessage<ServerOperationCode> message, IConnection connection) {
        //TODO
    }

    private void HandleSelectionResponse(IMessage<ServerOperationCode> message, IConnection connection) {
        //TODO
    }
}



回答7:


How about using static Dictionary and a virtual method to retrieve the static dictionaries in the inherited classes?

Like the follow for your case:

    public abstract class Operation
    {
        protected abstract Dictionary<string, int> getCodeTable();
        public int returnOpCode(string request){ return getCodeTable()[request]; }
    }
    public class ServerOperation : Operation
    {
        Dictionary<string, int> serverOpCodeTable = new Dictionary<string, int>()
        {
            {"LoginResponse", 0x00,},
            {"SelectionResponse", 0x01},
            {"BlahBlahResponse", 0x02}
        };
        protected override Dictionary<string, int> getCodeTable()
        {
            return serverOpCodeTable;
        }

    }
    public class ClientOperation : Operation
    {
        Dictionary<string, int> cilentOpCodeTable = new Dictionary<string, int>()
        {
            {"LoginResponse", 0x00,},
            {"SelectionResponse", 0x01},
            {"BlahBlahResponse", 0x02}
        };
        protected override Dictionary<string, int> getCodeTable()
        {
            return cilentOpCodeTable;
        }
    }


来源:https://stackoverflow.com/questions/3030911/how-can-i-make-an-abstract-enum-in-a-net-class-library

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!