C#: Enum dans les Interfaces
j'ai vu quelques fils semblables à cette question, mais aucun d'eux vraiment répondre à la question que je veux poser.
pour commencer, malheureusement je travaille avec du code API existant donc malheureusement, bien qu'il puisse y avoir une meilleure façon de faire ce que je demande, je suis bloqué à le faire de la même façon que la façon dont il est parce que rétrocompatibilité est non-négociable.
j'ai une classe de réponse qui contient actuellement un enum pour un code d'erreur et un description de la chaîne. Les codes d'erreur définissent un ensemble assez agréable et complet de réponses qui sont toutes très sémantiquement couplées aux opérations où ils sont utilisés.
malheureusement, je dois maintenant ajouter un flux de travail différent pour un ensemble similaire D'objets API, et cela nécessitera une description de chaîne, ce qui est très bien, mais aussi un code d'erreur enum consistant en un ensemble totalement sans rapport de codes d'erreur. Les codes d'erreur (et d'autres aspects du modèle d'objet) sera utilisé dans beaucoup de la même classe, donc il serait bien d'avoir une interface pour que je puisse exécuter les objets à travers le même framework.
Le but ici est de faire un contrat qui dit "j'ai un code d'erreur et une description du code d'erreur."
Cependant, pour autant que je sache, il n'y a aucun moyen d'ajouter un article à une interface comme
public interface IError
{
enum ErrorCode;
string Description;
}
il n'y a qu'une façon d'exprimer
public interface IError<T> where T: enum
{
T ErrorCode;
string Description;
}
n'importe qui à chaque course contre quelque chose comme ça avant?
5 réponses
Oui, je me suis heurté à ça. Pas dans cette situation particulière, mais dans d'autres questions de débordement de pile, comme celle-ci . (Je ne vote pas pour fermer celui-ci en double, car il est légèrement différent.)
Il est permet d'exprimer votre interface générique - mais pas en C#. Vous pouvez le faire à IL sans problèmes. J'espère que la limitation sera supprimée en C# 5. Le compilateur C# gère en fait la contrainte parfaitement correctement, autant que je l'ai vu.
si vous voulez vraiment opter pour cette option, vous pouvez utiliser un code similaire à celui de dans la mélodie , une bibliothèque que j'ai et qui expose diverses méthodes avec cette contrainte difficile à produire. Il utilise il rewriting, effectivement - il est brut, mais il fonctionne pour UM et serait probablement travailler pour vous aussi. Vous voudriez probablement mettre l'interface dans un assemblage séparé cependant, qui serait quelque peu maladroit.
bien sûr, vous pouvez faire votre interface juste avoir T : struct
à la place... ce ne serait pas idéal, mais cela restreindrait au moins le type un peu . Tant que vous pourrait s'assurer qu'il n'était pas maltraitée, qui fonctionne raisonnablement bien.
comme Jon Skeet l'a mentionné, la base IL supporte la contrainte des génériques pour être enums, cependant C# ne vous permet pas d'en profiter.
F# permet ce genre de contrainte, cependant. De plus, si l'interface est définie en F#, la contrainte sera appliquée en C# code qui implémente l'interface. Si vous êtes prêt à mélanger les langues dans votre solution, quelque chose comme ceci devrait fonctionner très bien:
type IError<'T when 'T :> System.Enum and 'T : struct> =
abstract member Error : 'T
abstract member Description : string
si vous mettez ceci un projet F# et le référencer à partir de votre projet C#, votre code C# qui implémente l'interface causera une erreur de C# compilateur sur toute tentative de l'utiliser avec un type non-enum.
vous pouvez suivre votre approche d'une manière légèrement différente:
public interface IError
{
Enum ErrorCode;
string Description;
}
System.Enum
est la classe de base de tous vos énums, donc qui devrait le gérer, mais est loin d'être expressif.
la bonne approche ici est de construire vos propres classes d'enum et une classe de base d'enum pour cela. Pour eg.,
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;
}
maintenant au lieu d'avoir vos propres erreurs, écrivez vos propres ErrorFlag
classe.
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 principales classes:
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 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
avoir laid d'avoir enum contraintes, voir quelqu'un connais une bonne solution de contournement pour le manque d'un enum contrainte générique?
l'incapacité d'écrire public interface IError<T> where T: enum
est quelque chose dont nous nous plaignons tous depuis des années. Jusqu'à présent il n'y a pas eu de progrès sur ce point.
je finis habituellement par écrire public interface IError<T>
et en laissant une note pour l'exécuteur que T doit être un enum.
si je comprends ce que vous voulez faire, alors oui, il n'y a aucun moyen de définir une interface qui contient comme l'un de ses membres un enum non spécifique. Votre second exemple est proche, mais vous êtes limité à limiter le type de T
à un struct
, ce qui permettrait n'importe quel type de valeur. À ce moment-là, vous avez juste à compter sur la connaissance de l'utilisation correcte des interfaces pour faire savoir aux gens le type attendu de T
devrait être un enum. Vous pourriez potentiellement faire il est un peu plus clair en définissant T
comme TEnum
ou TErrorValue
:
public interface IError<TEnum> where T: struct
{
T ErrorCode;
string Description;
}