2010-06-30 17 views
17

Ich habe ein paar ähnliche Threads zu dieser Frage gesehen, aber keine von ihnen beantwortet wirklich die Frage, die ich stellen möchte.C#: Enums in Interfaces

Für den Anfang, leider arbeite ich mit vorhandenen API-Code so traurig, während es eine bessere Möglichkeit gibt, zu tun, worum ich frage, bin ich dazu verdammt, es ähnlich zu tun, weil es weil Rückwärtskompatibilität ist nicht verhandelbar.

Ich habe eine Antwortklasse, die derzeit eine Enumeration für einen Fehlercode und eine Zeichenfolge Beschreibung enthält. Die Fehlercodes definieren einen ziemlich netten und vollständigen Satz von Antworten, die alle sehr semantisch mit den Operationen gekoppelt sind, in denen sie verwendet werden.

Leider muss ich jetzt einen anderen Workflow für einen ähnlichen Satz von API-Objekten hinzufügen, und dies erfordert eine String-Beschreibung, die in Ordnung ist, aber auch einen Enum-Fehlercode, der aus einem völlig unzusammenhängenden Satz von Fehlercodes besteht. Die Fehlercodes (und andere Aspekte des Objektmodells) werden in vielen derselben Klassen verwendet, daher wäre es schön, eine Schnittstelle zu erhalten, so dass ich die Objekte durch das gleiche Framework ausführen kann.

Die Absicht hier ist, einen Vertrag zu machen, der sagt "Ich habe einen Fehlercode und eine Beschreibung dieses Fehlercodes."

aber soweit ich weiß, gibt es keine Möglichkeit, ein Element an eine Schnittstelle wie

public interface IError 
{ 
    enum ErrorCode; 
    string Description; 
} 

noch ein Weg

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

Jeden jeder gegen etwas lief bis zum Ausdruck bringen wird hinzufügen So wie vorher?

+0

Welche Operationen werden Sie brauchen auf dem ErrorCode von dieser Schnittstelle durchgeführt werden? Wenn Sie keinen Zugriff auf Enum-spezifisches Verhalten benötigen, können Sie zulassen, dass der ErrorCode ein beliebiger Typ ohne Schaden ist. –

Antwort

13

Ja, ich bin dagegen gelaufen. Nicht in dieser speziellen Situation, aber in anderen Stack Overflow-Fragen, like this one. (Ich stimme nicht, um dieses als ein Duplikat zu schließen, da es etwas anders ist.)

Es ist möglich, Ihre generische Schnittstelle auszudrücken - gerade nicht in C#. Sie können es in IL ohne Probleme tun. Ich hoffe, dass die Beschränkung in C# 5 entfernt werden kann. Der C# -Compiler handhabt die Einschränkung tatsächlich korrekt, soweit ich gesehen habe.

Wenn Sie dies wirklich als Option verwenden möchten, könnten Sie einen ähnlichen Code wie in Unconstrained Melody verwenden, eine Bibliothek, die verschiedene Methoden mit dieser schwer zu erzeugenden Constraint verfügbar macht. Es nutzt effektiv IL-Rewriting - es ist grob, aber es funktioniert für UM und würde wahrscheinlich auch für Sie arbeiten. Wahrscheinlich möchten Sie die Schnittstelle jedoch in eine separate Assembly einfügen, was etwas peinlich wäre.

Natürlich könnten Sie Ihre Schnittstelle nur T : struct stattdessen haben ... wäre es nicht ideal, aber es würde zumindest den Typ etwas beschränken. Solange Sie sicherstellen konnten, dass es nicht missbraucht wurde, würde das einigermaßen gut funktionieren.

1

Die Unfähigkeit zu schreiben public interface IError<T> where T: enum ist etwas, über das wir uns alle seit Jahren beschwert haben. Bislang sind diesbezüglich keine Fortschritte zu verzeichnen.

Normalerweise schreibe ich public interface IError<T> und hinterlasse eine Notiz für den Implementor, dass T eine enum sein muss.

1

Wenn ich verstehe, was Sie tun wollen, dann ja, es gibt keine Möglichkeit, eine Schnittstelle zu definieren, die als eine ihrer Mitglieder eine unspezifische Enum enthält.Ihr zweites Beispiel ist in der Nähe, aber Sie sind auf Zwang die Art des T zu einem struct, die einem beliebigen Wert Typen erlauben würden. An diesem Punkt müssen Sie sich nur auf die Kenntnis der richtigen Schnittstellen verlassen, damit die Leute wissen, dass der erwartete Typ von T eine Enumeration sein sollte. Sie könnten möglicherweise durch die Definition T als TEnum oder TErrorValue machen es ein wenig klarer:

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

Als Jon Skeet erwähnt, unterstützt die Basis IL Generika beschränke Aufzählungen zu sein, aber C# nicht Sie Vorteil davon zu nehmen erlaubt.

F # tut dieser Art von Einschränkung erlauben, jedoch. Wenn die Schnittstelle in F # definiert ist, wird die Einschränkung in C# -Code erzwungen, der die Schnittstelle implementiert. Wenn Sie bereit sind Sprachen in Ihrer Lösung zu mischen, wie etwas, das sollte gut funktionieren:

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

Wenn Sie dies in einem F # -Projekt setzen und verweisen Sie es von Ihrem C# -Projekt, C# -Code, die die Schnittstelle implementiert verursacht einen C# -Compilerfehler bei jedem Versuch, es mit einem Nicht-Aufzählungstyp zu verwenden.

+0

Wie zugänglich ist diese Lösung für C# 2.0 und VS2005? Ich vermute ... nicht sehr? Obwohl es ziemlich süß klingt, bin ich mir nicht sicher, ob ich es ausnutzen kann. – bwerks

+1

Indirekt können Sie dies tun. Sie müssten die kostenlose [VS2008 Shell] (http://www.microsoft.com/downloads/details.aspx?FamilyId=40646580-97FA-4698-B65F-620D4B4B1ED7&displaylang=en) und [F # 2.0] (http installieren : //www.microsoft.com/downloads/details.aspx FamilyID = 444005fb-e627-4feb-b51d-13d6a3b4b8ed & displaylang = en). Dann könnten Sie Ihre Schnittstellen in einem F # -Projekt definieren, das auf CLR 2.0 abzielt, die Binärdateien erstellen und sie in C# 2 in VS2005 referenzieren. Wenn Sie Ihre Schnittstellen nicht oft ändern, können Sie meistens alles ignorieren, was Sie gerade installiert haben. –

+0

T muss auch als Werttyp festgelegt werden, andernfalls wäre System.Enum in Ordnung. –

3

Sie können auf eine etwas anderen Art und Weise mit Ihrem Ansatz gehen:

public interface IError 
{ 
    Enum ErrorCode; 
    string Description; 
} 

System.Enum sind die Basisklasse aller Ihre Aufzählungen, so dass damit umgehen soll, aber seinen weit von ausdruck zu sein.

Der richtige Ansatz ist hier Ihre eigenen Enum-Klassen und eine Basis Enum-Klasse für ihn zu bauen. Für z. B.

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

Jetzt stattdessen Ihre eigenen Fehler Aufzählungen zu haben, Ihre eigenen ErrorFlag Klassen schreiben.

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 

Jetzt ist Ihre Hauptklassen:

public class ReportError : IError 
{ 
    // implementation 
} 

public class DataError : IError 
{ 
    // implementation 
} 

Oder sonst,

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 

Um hässliche Art und Weise haben von Enum Einschränkungen hat, siehe Anyone know a good workaround for the lack of an enum generic constraint?