C #: Перечисления в интерфейсах - PullRequest
18 голосов
/ 01 июля 2010

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

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

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

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

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

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

public interface IError
{
    enum ErrorCode;
    string Description;
}

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

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

Кто-нибудь сталкивался с чем-то подобным раньше?

Ответы [ 5 ]

14 голосов
/ 01 июля 2010

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

В есть возможность выразить ваш общий интерфейс - только не в C #. Вы можете сделать это в IL без проблем. Я надеюсь, что ограничение может быть снято в C # 5. Насколько я видел, компилятор C # фактически обрабатывает ограничение совершенно корректно.

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

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

8 голосов
/ 01 июля 2010

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

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

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

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

3 голосов
/ 02 марта 2013

Вы можете использовать свой подход немного по-другому:

public interface IError
{
    Enum ErrorCode;
    string Description;
}

System.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

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

1 голос
/ 01 июля 2010

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

public interface IError<TEnum> where T: struct
{
    T ErrorCode;
    string Description;
}
1 голос
/ 01 июля 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...