简体   繁体   English

如何在.NET类库中创建“抽象”枚举?

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

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 (ie if (packet.OperationCode == ClientOperationCode.LoginRequest) ). 当你在自己的项目中工作时,这很好 - 你可以比较返回哪个枚举成员(即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. 因此,我有两个枚举添加为“抽象” - ServerOperationCode和ClientOperationCode。 I know it's not possible to implement abstract enums in C#. 我知道在C#中实现抽象枚举是不可能的。 How would I go doing this? 我该怎么做呢?

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. 假设packet.OperationCode返回一个字节,你可能不得不为字节实现一个==运算符。 put this code into your abstract OperationCode class. 将此代码放入您的抽象OperationCode类中。

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)

Why does everyone think that Enums cannot be abstracted? 为什么每个人都认为Enums不能抽象?

The class System.Enum IS the abstraction of an enumeration. 类System.Enum 枚举的抽象。

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) 我使用Enum.GetName(Type, object)来获取枚举值的名称(为属性提供名称),出于速度和一致性的原因,我使用GetHashCode()返回枚举成员的整数值( int的哈希码总是只是int值)

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);
    }

How about using static Dictionary and a virtual method to retrieve the static dictionaries in the inherited classes? 如何使用静态Dictionary和虚方法来检索继承类中的静态字典?

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;
        }
    }

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 . 如果您想要一个可以由库的客户端扩展的枚举,请查看我的CodeProject文章主题, 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. 请注意,在我的库中,Symbol会自动选择“枚举值”的ID号,因为它设计用于单个程序,而不是用于在网络上交换值。 Perhaps it would be possible, however, to alter Symbol.cs to your liking so that clients can assign constant values to symbols. 但是,也许可以根据自己的喜好更改Symbol.cs,以便客户端可以为符号分配常量值。

  1. Create an Enum for LoginResponse, SelectionResponse, etc., but don't specify the values. 为LoginResponse,SelectionResponse等创建枚举,但指定值。

  2. Have ServerOperationCode and ClientOperationCode implement a function that, given an integer bytecode, returns the appropriate value from your Enum. 让ServerOperationCode和ClientOperationCode实现一个函数,给定整数字节码,从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. 警告:由于接口无法定义静态函数,因此如果所述函数是实例函数,ServerOperationCode和ClientOperationCode将只能实现公共接口。 If they don't need to implement a common interface, GetOperationCode can be a static function. 如果他们不需要实现通用接口,则GetOperationCode可以是静态函数。

(Apologies for any C# snafus, it's not my first language...) (对任何C#snafus道歉,这不是我的第一语言......)

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. 表结构只包含一个整数值(ID)和一个字符串(名称),该表可以由应用程序的任何一方(客户端或服务器)填写,并由另一方读取。 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; 您也可以在app.config文件中实现相同的功能; force the user of your library to set these values in the app.config file which your library can access easily. 强制您的库的用户在您的库可以轻松访问的app.config文件中设置这些值。

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. 这个问题的主要问题是你不能将你的泛型约束到枚举类型,但只能说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. 有人可能会使用其他一些原始类型来实例化你的类型(尽管使用int仍然可以起作用,前提是它们都是唯一值。如果它们不是,则字典会抛出异常。你可以使用反射添加一些额外的检查。确保你通过枚举。

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
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM