2010-06-30 2 views
17

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

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

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

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

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

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

public interface IError 
{ 
    enum ErrorCode; 
    string Description; 
} 

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

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

Любой каждый нарываться на что-то как раньше?

+0

Какие операции вам понадобятся выполнить на ErrorCode, предоставленном этим интерфейсом? Если вам не нужен доступ к специфичному для Enum поведению, вы можете позволить ErrorCode быть любым типом без какого-либо вреда. –

ответ

13

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

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

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

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

1

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

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

1

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

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

Как уже упоминалось Джон Скит, база 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# при любой попытке использовать его с типом non-enum.

+0

Насколько доступно это решение для C# 2.0 и VS2005? Я предполагаю ... не очень? Пока звучание довольно сладкое, я не уверен, что смогу воспользоваться. – bwerks

+1

Косвенно вы можете это сделать. Вам нужно будет установить бесплатную [VS2008 Shell] (http://www.microsoft.com/downloads/details.aspx?FamilyId=40646580-97FA-4698-B65F-620D4B4B1ED7&displaylang=en) и [F # 2.0] (http : //www.microsoft.com/downloads/details.aspx FamilyID = 444005fb-e627-4feb-b51d-13d6a3b4b8ed & DisplayLang = ен). Затем вы можете определить свои интерфейсы в проекте F #, ориентированном на CLR 2.0, построить двоичные файлы и ссылаться на них с C# 2 в VS2005. Если вы не часто меняете свои интерфейсы, вы можете в основном игнорировать все, что вы только что установили. –

+0

T также должен быть ограничен типом значения, иначе System.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 

Чтобы иметь некрасивый способ иметь перечислений ограничения, см Anyone know a good workaround for the lack of an enum generic constraint?

Смежные вопросы