Как я могу сделать "абстрактное" перечисление в библиотеке классов .NET?

Я делаю серверную библиотеку, в которой ассоциация пакетов выполняется перечислением.

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

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

Это прекрасно работает, когда вы работаете в своем собственном проекте - вы можете сравнить, какой член перечисления возвращается (т.е. if (packet.OperationCode == ClientOperationCode.LoginRequest)). Однако, поскольку это библиотека классов, пользователь должен будет определить собственное перечисление.

Поэтому у меня есть два перечисления, которые нужно добавить как "abstract" - ServerOperationCode и ClientOperationCode. Я знаю, что невозможно реализовать абстрактные перечисления в С#. Как я буду делать это?

Ответ 1

Мне нравится использовать статические экземпляры на моих классах, когда мне это нужно. Он позволяет иметь некоторые значения по умолчанию, но также позволяет расширять его с помощью обычных средств наследования и реализации интерфейса:

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

Предполагая, что packet.OperationCode возвращает байт, вам, скорее всего, придется реализовать оператор == для байта. поместите этот код в свой абстрактный класс 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);
}

это позволит вам иметь ту же проверку, что и вы показали:

if (packet.OperationCode == ClientOperationCode.LoginRequest)

Ответ 2

Почему все думают, что Enums нельзя абстрагировать?

Класс System.Enum - это абстракция перечисления.

Вы можете назначить любое значение перечисления для Enum, и вы можете вернуть его обратно в исходное перечисление или использовать имя или значение.

например:

Этот небольшой фрагмент кода относится к динамической коллекции свойств, используемой в одной из моих библиотек управления. Я разрешаю создание свойств и доступ к ним через значение перечисления, чтобы сделать его несколько быстрее, и меньше человеческих ошибок

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

Я использую Enum.GetName(Type, object), чтобы получить имя значения перечисления (указать имя для свойства), а также по причинам скорости и последовательности я использую GetHashCode() для возврата целочисленного значения элемента перечисления (хеш-код для int всегда просто значение int)

Это пример вызываемого метода:

    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

Если вы хотите сказать, что хотите перечислить, которые могут быть расширены клиентами библиотеки, ознакомьтесь с моей статьей CodeProject по теме Символы как расширяемые перечисления.

Обратите внимание, что в моей библиотеке Symbol автоматически выбирает идентификационные номера для "значений перечисления", поскольку она предназначена для использования внутри одной программы, а не для обмена значениями в сети. Возможно, было бы возможно, однако, изменить Symbol.cs по своему вкусу, чтобы клиенты могли назначать постоянные значения символам.

Ответ 4

  • Создайте Enum для LoginResponse, SelectionResponse и т.д., но не указывайте значения.

  • Имейте ServerOperationCode и ClientOperationCode реализовать функцию, которая, учитывая целочисленный байт-код, возвращает соответствующее значение из вашего Enum.

Пример:

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;
      [...]
    } 
}

Предостережение: поскольку интерфейсы не могут определять статические функции, ServerOperationCode и ClientOperationCode смогут реализовать только общий интерфейс, если указанная функция является функцией экземпляра. Если им не нужно реализовывать общий интерфейс, GetOperationCode может быть статической функцией.

(Извиняется за любой С# snafus, это не мой первый язык...)

Ответ 5

Если есть база данных, которая используется совместно с вашим клиентом и серверным приложением, могут помочь справочные таблицы; структура таблицы просто содержит целочисленное значение (ID) и строку (имя), эта таблица может быть заполнена по обе стороны приложения (клиента или сервера) и считана другой. Вы можете кэшировать эту таблицу (в своем коде) в словаре для быстрого поиска.

Вы также можете реализовать то же самое в файле app.config; заставите пользователя вашей библиотеки установить эти значения в файле app.config, доступ к которому может получить ваша библиотека.

Ответ 6

Я написал библиотеку переключения сообщений с похожим сценарием некоторое время назад, и я решил использовать generics для передачи пользовательского перечисления. Основная проблема заключается в том, что вы не можете ограничить свой общий тип только перечисляемыми типами, но можете только сказать, когда T: struct. Кто-то может создать экземпляр вашего типа с помощью некоторого другого примитивного типа (хотя использование ints все еще может быть функциональным, если все они являются уникальными значениями. Словарь генерирует исключение, если это не так. Вы могли бы добавить дополнительную проверку, используя отражение убедитесь, что вы перечислили перечисление.

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

Учитывая ваш примерный сценарий, вы просто подклассифицируете его так.

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

Как использовать статический словарь и виртуальный метод для извлечения статических словарей в унаследованных классах?

Как и для вашего случая:

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