C #: Enums in Interfaces

J’ai déjà vu quelques fils similaires à cette question, mais aucun ne répond vraiment à la question que je veux poser.

Pour commencer, malheureusement, je travaille avec le code API existant si sortingstement, bien qu’il puisse exister un meilleur moyen de faire ce que je demande, je suis obligé de le faire de la même façon, car la compatibilité en amont n’est pas compatible. -négociable.

J’ai une classe de réponse qui contient actuellement une enum pour un code d’erreur et une description de chaîne. Les codes d’erreur définissent un ensemble assez agréable et complet de réponses, toutes très couplées sémantiquement aux opérations où elles sont utilisées.

Malheureusement, je dois maintenant append un stream de travail différent pour un ensemble similaire d’objects API, ce qui nécessitera une description de la chaîne, ce qui est correct, mais également un code d’erreur enum consistant en un ensemble totalement indépendant de codes d’erreur. Les codes d’erreur (et d’autres aspects du modèle object) seront utilisés dans de nombreuses classes identiques. Il serait donc intéressant de disposer d’une interface active pour pouvoir exécuter les objects dans le même cadre.

L’intention ici est de faire un contrat qui dit “J’ai un code d’erreur et une description de ce code d’erreur”.

Cependant, pour autant que je sache, il est impossible d’append un élément à une interface telle que

public interface IError { enum ErrorCode; ssortingng Description; } 

il n’y a pas non plus moyen d’exprimer

 public interface IError where T: enum { T ErrorCode; ssortingng Description; } 

Tout le monde se heurte à quelque chose comme ça avant?

Oui, je me suis heurté à cela. Pas dans cette situation particulière, mais dans d’autres questions Stack Overflow, comme celle-ci . (Je ne vote pas pour fermer celui-ci en double, car il est légèrement différent.)

Il est possible d’exprimer votre interface générique – mais pas en C #. Vous pouvez le faire en IL sans aucun problème. J’espère que la limitation sera supprimée en C # 5. Le compilateur C # gère la contrainte de manière parfaitement correcte, pour autant que je sache.

Si vous voulez vraiment opter pour cette option, vous pouvez utiliser un code similaire à celui de Unconstrained Melody , une bibliothèque que j’ai, qui expose diverses méthodes avec cette contrainte difficile à produire. Il utilise la réécriture IL, de manière efficace – c’est brutal, mais cela fonctionne pour l’UM et fonctionnerait probablement pour vous aussi. Vous voudrez probablement mettre l’interface dans une assemblée séparée, ce qui serait un peu gênant.

Bien sûr, vous pourriez faire en sorte que votre interface ait simplement T : struct place … ce ne serait pas idéal, mais au moins, cela contraindrait quelque peu le type. Tant que vous pouvez vous assurer que cela ne sera pas maltraité, cela fonctionnerait raisonnablement bien.

Comme Jon Skeet l’a mentionné, la base IL prend en charge les génériques contraignants, mais C # ne vous permet pas d’en tirer parti.

F # permet cependant ce type de contrainte. De plus, si l’interface est définie dans F #, la contrainte sera appliquée dans le code C # qui implémente l’interface. Si vous êtes prêt à mélanger les langues dans votre solution, cela devrait fonctionner correctement:

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

Si vous mettez cela dans un projet F # et que vous le référencez à partir de votre projet C #, votre code C # qui implémente l’interface provoquera une erreur du compilateur C # lors de toute tentative d’utilisation avec un type non-enum.

Vous pouvez suivre votre approche d’une manière légèrement différente:

 public interface IError { Enum ErrorCode; ssortingng Description; } 

System.Enum est la classe de base de tous vos enums, elle devrait donc la gérer, mais elle est loin d’être expressive.

La bonne approche consiste ici à créer vos propres classes d’énumération et une classe d’énumération de base. Par exemple,

 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 ssortingng ToSsortingng() { return value.ToSsortingng(); } } public interface IError { ErrorFlag ErrorCode; ssortingng Description; } 

Maintenant, au lieu d’avoir vos propres énumérations d’erreur, écrivez vos propres classes 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 

Maintenant vos classes principales:

 public class ReportError : IError { // implementation } public class DataError : IError { // implementation } 

Ou autrement,

 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 ssortingng ToSsortingng() { return value.ToSsortingng(); } } public interface IError where T : ErrorFlag { T ErrorCode { get; set; } ssortingng 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 

Pour avoir la mauvaise manière d’avoir des contraintes d’énumération, voir Quelqu’un connaît-il une bonne solution de contournement pour l’absence de contrainte générique d’énumération?

L’incapacité d’écrire public interface IError where T: enum est quelque chose dont nous nous plaignons depuis des années. Jusqu’à présent, il n’y a eu aucun progrès à cet égard.

Je public interface IError généralement par écrire public interface IError et laisser une note à l’implémenteur que T doit être une énumération.

Si je comprends ce que vous voulez faire, alors oui, il n’ya aucun moyen de définir une interface qui contient comme un de ses membres une énumération non spécifique. Votre deuxième exemple est proche, mais vous êtes limité à la contrainte du type de T à une struct , qui autoriserait tout type de valeur. À ce stade, vous devez simplement vous fier à la connaissance de l’utilisation correcte des interfaces pour que les utilisateurs sachent que le type de T attendu devrait être une énumération. Vous pourriez potentiellement le rendre un peu plus clair en définissant T comme TEnum ou TErrorValue :

 public interface IError where T: struct { T ErrorCode; ssortingng Description; }