Как сделать «абстрактный» перечисление в библиотеке классов .NET? - PullRequest
11 голосов
/ 13 июня 2010

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

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)). Однако, поскольку это библиотека классов, пользователь должен будет определить свое собственное перечисление.

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

Ответы [ 7 ]

13 голосов
/ 13 июня 2010

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

    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)
6 голосов
/ 24 мая 2013

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

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

Вы можете назначить любое значение перечисления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);
    }
0 голосов
/ 21 января 2014

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

Как и в вашем случае:

    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;
        }
    }
0 голосов
/ 13 июня 2010

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

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
    }
}
0 голосов
/ 13 июня 2010

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

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

0 голосов
/ 13 июня 2010
  1. Создает Enum для LoginResponse, SelectionResponse и т. Д., Но не задает значения.

  2. Имеют 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;
      [...]
    } 
}

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

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

0 голосов
/ 13 июня 2010

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

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

...