2010-06-30 13 views
17

Ho visto un paio di thread simili a questa domanda, ma nessuno di loro risponde veramente alla domanda che voglio porre.C#: Enums in Interfaces

Per i principianti, sfortunatamente sto lavorando con il codice API esistente così tristemente, mentre potrebbe esserci un modo migliore per fare quello che sto chiedendo, sono bloccato a farlo in modo simile a come lo è perché la retrocompatibilità non è negoziabile.

Ho una classe di risposta che contiene attualmente un enum per un codice di errore e una descrizione di stringa. I codici di errore definiscono un insieme di risposte abbastanza bello e completo che sono tutte molto semanticamente collegate alle operazioni in cui vengono utilizzate.

Sfortunatamente, ora devo aggiungere un flusso di lavoro diverso per un insieme simile di oggetti API, e ciò richiederà una descrizione di stringa, che va bene, ma anche un codice di errore enum consistente in un set di codici di errore totalmente non correlato. I codici di errore (e altri aspetti del modello di oggetti) saranno usati in molte delle stesse classi, quindi sarebbe bello avere un'interfaccia che possa far girare gli oggetti attraverso lo stesso framework.

L'intento qui è quello di fare un contratto che dice "Ho un codice di errore, e una descrizione di quel codice di errore".

Tuttavia, per quanto ne so non c'è modo per aggiungere un elemento a un'interfaccia come

public interface IError 
{ 
    enum ErrorCode; 
    string Description; 
} 

né v'è un modo per esprimere

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

Chiunque ogni corsa contro qualcosa come prima?

+0

Quali operazioni avrete bisogno eseguire sul ErrorCode fornito da questa interfaccia? Se non è necessario accedere al comportamento specifico di Enum, è possibile consentire a ErrorCode di essere di qualsiasi tipo senza danni. –

risposta

13

Sì, mi sono imbattuto in questo. Non in questa particolare situazione, ma in altre domande Stack Overflow, like this one. (Non sto votando per chiuderlo come duplicato, poiché è leggermente diverso.)

E è possibile esprimere la tua interfaccia generica - non solo in C#. Puoi farlo in IL senza problemi. Spero che la limitazione possa essere rimossa in C# 5. Il compilatore C# gestisce effettivamente il vincolo in modo perfettamente corretto, per quanto ho visto.

Se si desidera utilizzare questa opzione come opzione, è possibile utilizzare un codice simile a quello in Unconstrained Melody, una libreria che contiene vari metodi con questo vincolo difficile da produrre. Usa la riscrittura di IL, in modo efficace - è rozzo, ma funziona per la messaggistica unificata e probabilmente funzionerebbe anche per te. Probabilmente vorrai mettere l'interfaccia in un assieme separato, il che sarebbe un po 'imbarazzante.

Naturalmente, si potrebbe fare l'interfaccia basta T : struct invece ... non sarebbe l'ideale, ma sarebbe almeno limitare il tipo po. Finché potevi assicurarti che non venisse abusato, funzionerebbe abbastanza bene.

1

L'impossibilità di scrivere public interface IError<T> where T: enum è qualcosa di cui ci lamentiamo da anni. Finora non ci sono stati progressi in merito.

Di solito finisco per scrivere public interface IError<T> e lasciare una nota per l'implementatore che T deve essere un enum.

1

Se capisco cosa si vuole fare, allora sì, non c'è modo di definire un'interfaccia che contenga come uno dei suoi membri un enum non specifico.Il tuo secondo esempio è vicino, ma sei limitato a limitare il tipo di T a struct, che consentirebbe qualsiasi tipo di valore. A quel punto, devi solo fare affidamento sulla conoscenza delle interfacce per l'uso appropriato per far sapere alla gente che il tipo previsto di T dovrebbe essere un enume. Si potrebbe potenzialmente rendere un po 'più chiaro definendo T come TEnum o TErrorValue:

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

Come detto Jon Skeet, l'IL base supporta vincolante farmaci generici per essere enumerazioni, ma C# non consente di trarre vantaggio da esso.

F # consente tuttavia questo tipo di vincolo. Inoltre, se l'interfaccia è definita in F #, il vincolo verrà applicato nel codice C# che implementa l'interfaccia. Se siete disposti a mescolare lingue nella soluzione, qualcosa come questo dovrebbe funzionare bene:

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

Se si mette questo in un # progetto di F e riferimento dal vostro progetto C#, il vostro codice C# che implementa l'interfaccia causerà un errore del compilatore C# su qualsiasi tentativo di utilizzarlo con un tipo non enum.

+0

Quanto è accessibile questa soluzione a C# 2.0 e VS2005? Sto indovinando ... non molto? Mentre suona piuttosto dolce, non sono sicuro che riuscirò a trarne vantaggio. – bwerks

+1

Indirettamente, puoi farlo. Dovresti installare gratuitamente [VS2008 Shell] (http://www.microsoft.com/downloads/details.aspx?FamilyId=40646580-97FA-4698-B65F-620D4B4B1ED7&displaylang=en) e [F # 2.0] (http :? //www.microsoft.com/downloads/details.aspx FamilyID = 444005fb-e627-4feb-b51d-13d6a3b4b8ed & displaylang = it). Quindi è possibile definire le interfacce in un progetto F # che mira a CLR 2.0, creare i file binari e farvi riferimento da C# 2 in VS2005. Se non cambi le tue interfacce spesso, puoi quindi ignorare quasi tutto ciò che hai appena installato. –

+0

Anche T deve essere vincolato per essere un tipo di valore, altrimenti System.Enum andrebbe bene. –

3

si può andare con il vostro approccio in un modo leggermente diverso:

public interface IError 
{ 
    Enum ErrorCode; 
    string Description; 
} 

System.Enum è la classe base di tutte le enumerazioni, in modo che dovrebbe gestire la cosa, ma la sua lungi dall'essere espressiva.

L'approccio giusto qui è quello di costruire le proprie classi di enumerazione e una classe di enum base per questo. Per per esempio.,

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

Ora, invece di avere il proprio enumerazioni di errore, scrivere i propri ErrorFlag classi.

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 

Ora le tue classi principali:

public class ReportError : IError 
{ 
    // implementation 
} 

public class DataError : IError 
{ 
    // implementation 
} 

o in altro modo,

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 

Per avere brutto modo di avere vincoli enum, vedere Anyone know a good workaround for the lack of an enum generic constraint?