Подтвердить что ты не робот

С#: перечисления в интерфейсах

Я видел пару подобных потоков для этого вопроса, но никто из них не ответил на вопрос, который я хочу задать.

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

У меня есть класс ответа, который в настоящее время содержит перечисление для кода ошибки и описания строки. Коды ошибок определяют довольно хороший и полный набор ответов, которые все очень семантически связаны с операциями, в которых они используются.

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

Целью здесь является заключение контракта, в котором говорится: "У меня есть код ошибки и описание этого кода ошибки".

Однако, насколько я знаю, нет способа добавить элемент к интерфейсу, например

public interface IError
{
    enum ErrorCode;
    string Description;
}

и не существует способа выразить

public interface IError<T> where T: enum
{
    T ErrorCode;
    string Description;
}

Кто-нибудь каждый раз сталкивается с чем-то вроде этого?

4b9b3361

Ответ 1

Да, я столкнулся с этим. Не в этой конкретной ситуации, но в других вопросах, как этот. (Я не голосую, чтобы закрыть это как дубликат, так как он немного отличается.)

Можно выразить свой общий интерфейс - просто не в С#. Вы можете сделать это в ИЛ без проблем. Я надеюсь, что ограничение может быть удалено на С# 5. Компилятор С# фактически корректно обрабатывает ограничение, насколько я видел.

Если вы действительно хотите использовать это как вариант, вы можете использовать код, похожий на код в Unconstrained Melody, библиотеку я ' который предоставил различные методы с этим трудным производственным ограничением. Он использует IL-переписывание, эффективно - это грубо, но он работает для UM и, вероятно, будет работать и для вас. Вы, вероятно, захотите поместить интерфейс в отдельную сборку, хотя это будет несколько неудобно.

Конечно, вы могли бы сделать свой интерфейс просто T : struct вместо этого... он не был бы идеальным, но он, по крайней мере, сдерживал бы тип. До тех пор, пока вы можете убедиться, что это не злоупотребление, это будет работать достаточно хорошо.

Ответ 2

Как упоминал Джон Скит, базовый IL поддерживает ограничение генериков как перечислений, однако С# не позволяет вам воспользоваться им.

Однако

F # допускает такое ограничение. Кроме того, если интерфейс определен в F #, ограничение будет принудительно введено в коде С#, который реализует интерфейс. Если вы готовы смешивать языки в своем решении, что-то вроде этого должно работать нормально:

type IError<'T when 'T :> System.Enum and 'T : struct> =
    abstract member Error : 'T
    abstract member Description : string

Если вы помещаете это в проект F # и ссылаетесь на него из своего проекта С#, ваш код С#, реализующий интерфейс, вызовет ошибку компилятора С# при любой попытке использовать его с типом, отличным от enum.

Ответ 3

Вы можете пойти с вашим подходом несколько иначе:

public interface IError
{
    Enum ErrorCode;
    string Description;
}

System.Enum является базовым классом всех ваших перечислений, поэтому он должен обрабатывать его, но он не является выразительным.

Правильный подход заключается в создании собственных классов enum и базового класса enum для него. Например,

public class ErrorFlag // base enum class
{
    int value;

    ErrorFlag() 
    {

    }

    public static implicit operator ErrorFlag(int i)
    {
        return new ErrorFlag { value = i };
    }

    public bool Equals(ErrorFlag other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        return value == other.value;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as ErrorFlag);
    }

    public static bool operator ==(ErrorFlag lhs, ErrorFlag rhs)
    {
        if (ReferenceEquals(lhs, null))
            return ReferenceEquals(rhs, null);

        return lhs.Equals(rhs);
    }

    public static bool operator !=(ErrorFlag lhs, ErrorFlag rhs)
    {
        return !(lhs == rhs);
    }

    public override int GetHashCode()
    {
        return value;
    }

    public override string ToString()
    {
        return value.ToString();
    }
}

public interface IError
{
    ErrorFlag ErrorCode;
    string Description;
}

Теперь вместо того, чтобы иметь собственные перечисления ошибок, напишите свои собственные классы ErrorFlag.

public sealed class ReportErrorFlag : ErrorFlag
{
    //basically your enum values
    public static readonly ErrorFlag Report1 = 1;
    public static readonly ErrorFlag Report2 = 2;
    public static readonly ErrorFlag Report3 = 3;

    ReportErrorFlag() 
    {

    }
}

public sealed class DataErrorFlag : ErrorFlag
{
    //basically your enum values
    public static readonly ErrorFlag Data1 = 1;
    public static readonly ErrorFlag Data2 = 2;
    public static readonly ErrorFlag Data3 = 3;

    DataErrorFlag() 
    {

    }
}

// etc

Теперь ваши основные классы:

public class ReportError : IError
{
    // implementation
}

public class DataError : IError
{
    // implementation
}

Или иначе,

public class ErrorFlag // base enum class
{
    internal int value { get; set; }

    public bool Equals(ErrorFlag other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        return value == other.value;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as ErrorFlag);
    }

    public static bool operator ==(ErrorFlag lhs, ErrorFlag rhs)
    {
        if (ReferenceEquals(lhs, null))
            return ReferenceEquals(rhs, null);

        return lhs.Equals(rhs);
    }

    public static bool operator !=(ErrorFlag lhs, ErrorFlag rhs)
    {
        return !(lhs == rhs);
    }

    public override int GetHashCode()
    {
        return value;
    }

    public override string ToString()
    {
        return value.ToString();
    }
}

public interface IError<T> where T : ErrorFlag
{
    T ErrorCode { get; set; }
    string Description { get; set; }
}

//enum classes
public sealed class ReportErrorFlag : ErrorFlag
{
    //basically your enum values
    public static readonly ReportErrorFlag Report1 = new ReportErrorFlag { value = 1 };
    public static readonly ReportErrorFlag Report2 = new ReportErrorFlag { value = 2 };
    public static readonly ReportErrorFlag Report3 = new ReportErrorFlag { value = 3 };

    ReportErrorFlag()
    {

    }
}

public sealed class DataErrorFlag : ErrorFlag
{
    //basically your enum values
    public static readonly DataErrorFlag Data1 = new DataErrorFlag { value = 1 };
    public static readonly DataErrorFlag Data2 = new DataErrorFlag { value = 2 };
    public static readonly DataErrorFlag Data3 = new DataErrorFlag { value = 3 };

    DataErrorFlag()
    {

    }
}

//implement the rest

Чтобы иметь уродливый способ ограничения перечислений, см. Кто-нибудь знает обходное решение об отсутствии общего ограничения перечисления?

Ответ 4

Невозможность написать public interface IError<T> where T: enum - это то, о чем мы все жаловались годами. Пока не было достигнуто никакого прогресса.

Обычно я пишу public interface IError<T> и оставляю записку для разработчика, что T должен быть перечислением.

Ответ 5

Если я понимаю, что вы хотите сделать, тогда да, нет способа определить интерфейс, содержащий как один из его членов неспецифическое перечисление. Второй пример близок, но вы ограничены ограничением типа T на struct, что позволит использовать любой тип значения. В этот момент вам просто нужно полагаться на знание правильного использования интерфейсов, чтобы люди знали, что ожидаемый тип T должен быть перечислением. Вы могли бы сделать это немного яснее, указав T как TEnum или TErrorValue:

public interface IError<TEnum> where T: struct
{
    T ErrorCode;
    string Description;
}