Ich verstehe, dass [Flag]
Aufzählungen normalerweise für Dinge verwendet werden, die kombiniert werden können, wobei sich die einzelnen Werte nicht gegenseitig ausschließen .
Beispielsweise:
[Flags]
public enum SomeAttributes
{
Foo = 1 << 0,
Bar = 1 << 1,
Baz = 1 << 2,
}
Wo jeder SomeAttributes
Wert eine Kombination von Foo
, Bar
und sein kann Baz
.
In einem komplizierteren, realistischen Szenario benutze ich eine Aufzählung, um Folgendes zu beschreiben DeclarationType
:
[Flags]
public enum DeclarationType
{
Project = 1 << 0,
Module = 1 << 1,
ProceduralModule = 1 << 2 | Module,
ClassModule = 1 << 3 | Module,
UserForm = 1 << 4 | ClassModule,
Document = 1 << 5 | ClassModule,
ModuleOption = 1 << 6,
Member = 1 << 7,
Procedure = 1 << 8 | Member,
Function = 1 << 9 | Member,
Property = 1 << 10 | Member,
PropertyGet = 1 << 11 | Property | Function,
PropertyLet = 1 << 12 | Property | Procedure,
PropertySet = 1 << 13 | Property | Procedure,
Parameter = 1 << 14,
Variable = 1 << 15,
Control = 1 << 16 | Variable,
Constant = 1 << 17,
Enumeration = 1 << 18,
EnumerationMember = 1 << 19,
Event = 1 << 20,
UserDefinedType = 1 << 21,
UserDefinedTypeMember = 1 << 22,
LibraryFunction = 1 << 23 | Function,
LibraryProcedure = 1 << 24 | Procedure,
LineLabel = 1 << 25,
UnresolvedMember = 1 << 26,
BracketedExpression = 1 << 27,
ComAlias = 1 << 28
}
Offensichtlich kann eine gegebene Declaration
Zahl nicht gleichzeitig a Variable
und a sein LibraryProcedure
- die beiden Einzelwerte können nicht kombiniert werden. Und sie sind es auch nicht.
Diese Flags sind zwar äußerst nützlich (es ist sehr einfach zu überprüfen, ob ein gegebenes DeclarationType
a Property
oder a ist Module
), sie fühlen sich jedoch "falsch" an, da die Flags nicht wirklich zum Kombinieren von Werten, sondern zum Gruppieren in "Untertypen" verwendet werden.
Mir wurde gesagt, dass dies Enum-Flags missbraucht - diese Antwort besagt im Wesentlichen, dass ich für Äpfel einen anderen Enum-Typ und für Orangen einen anderen Enum-Typ benötige - die Das Problem dabei ist, dass alle Deklarationen eine gemeinsame Schnittstelle haben müssen, damit DeclarationType
sie in der Basisklasse Declaration
verfügbar gemacht werden können: Eine PropertyType
Aufzählung wäre überhaupt nicht nützlich.
Ist das ein schlampiges / überraschendes / missbräuchliches Design? Wenn ja, wie wird dieses Problem normalerweise gelöst?
quelle
DeclarationType
. Wenn ich herausfinden will, ob es sichx
um einen Untertyp handelty
, möchte ich das wahrscheinlich als schreibenx.IsSubtypeOf(y)
, nicht alsx && y == y
.x.HasFlag(DeclarationType.Member)
....HasFlag
statt aufgerufen wirdIsSubtypeOf
, muss ich auf andere Weise herausfinden, dass das, was es wirklich bedeutet, "Subtyp von" ist. Sie könnten eine Erweiterungsmethode erstellen, aber als Benutzer würde es mich am wenigsten überraschenDeclarationType
, nur eine Struktur zu sein, dieIsSubtypeOf
eine echte Methode darstellt.Antworten:
Dies missbraucht definitiv Aufzählungen und Flaggen! Es könnte für Sie funktionieren, aber jeder andere, der den Code liest, wird stark verwirrt sein.
Wenn ich das richtig verstehe, haben Sie eine hierarchische Klassifizierung der Deklarationen. Dies sind viel zu viele Informationen, um sie in einer einzigen Aufzählung zu kodieren. Aber es gibt eine offensichtliche Alternative: Verwenden Sie Klassen und Vererbung! Also
Member
erbt vonDeclarationType
,Property
erbt vonMember
und so weiter.Aufzählungen sind unter bestimmten Umständen angebracht: Wenn ein Wert immer einer begrenzten Anzahl von Optionen entspricht oder wenn es sich um eine beliebige Kombination einer begrenzten Anzahl von Optionen (Flags) handelt. Informationen, die komplexer oder strukturierter sind, sollten mithilfe von Objekten dargestellt werden.
Bearbeiten : In Ihrem "realen Szenario" scheint es mehrere Stellen zu geben, an denen das Verhalten abhängig vom Wert der Aufzählung ausgewählt wird. Dies ist wirklich ein Gegenmuster, da Sie
switch
+enum
als "Polymorphismus des armen Mannes" verwenden. Verwandeln Sie den Enum-Wert einfach in verschiedene Klassen, die das deklarationsspezifische Verhalten einschließen, und Ihr Code wird viel saubererquelle
ParserRuleContext
Typ der generierten Klasse zu tun als mit der Aufzählung. Dieser Code versucht, die Token-Position abzurufen, an der eineAs {Type}
Klausel in die Deklaration eingefügt werden soll. DieseParserRuleContext
abgeleiteten Klassen werden von Antr4 anhand einer Grammatik generiert, die die Parserregeln definiert. Ich kontrolliere die [eher flache] Vererbungshierarchie der Analysebaumknoten nicht wirklich, obwohl ich ihrepartial
Fähigkeit nutzen kann, um Schnittstellen zu implementieren, z machen sie etwasAsTypeClauseTokenPosition
Eigentum aussetzen .. viel Arbeit.Ich finde diesen Ansatz leicht zu lesen und zu verstehen. IMHO, es ist nicht verwirrend. Davon abgesehen habe ich einige Bedenken hinsichtlich dieses Ansatzes:
Mein Hauptvorbehalt ist, dass es keine Möglichkeit gibt, dies durchzusetzen:
Während Sie die obige Kombination nicht deklarieren, ist dieser Code
Ist völlig gültig. Und es gibt keine Möglichkeit, diese Fehler beim Kompilieren zu erfassen.
Es gibt eine Begrenzung für die Menge an Informationen, die Sie in Bit-Flags codieren können. Was sind 64 Bits? Im
int
Moment nähern Sie sich in gefährlicher Weise der Größe von und wenn diese Enumeration weiter zunimmt, werden Ihnen möglicherweise irgendwann die Teile ausgehen ...Fazit ist, ich denke, dies ist ein gültiger Ansatz, aber ich würde zögern, ihn für große / komplexe Hierarchien von Flags zu verwenden.
quelle
int
ist trotzdem ein echtes Problem.TL; DR Nach ganz unten scrollen.
Soweit ich weiß, implementieren Sie eine neue Sprache auf C #. Die Aufzählungen scheinen den Typ eines Bezeichners (oder alles, was einen Namen hat und im Quellcode der neuen Sprache erscheint) zu bezeichnen, der auf Knoten angewendet wird, die einer Baumdarstellung des Programms hinzugefügt werden sollen.
In dieser besonderen Situation gibt es sehr wenige polymorphe Verhaltensweisen zwischen den verschiedenen Knotentypen. Mit anderen Worten, während es erforderlich ist, dass der Baum Knoten sehr unterschiedlicher Typen (Varianten) enthalten kann, greift der tatsächliche Besuch dieser Knoten im Wesentlichen auf eine riesige Wenn-Dann-Sonst-Kette (oder
instanceof
/is
Schecks) zurück. Diese riesigen Kontrollen werden wahrscheinlich an vielen verschiedenen Stellen im gesamten Projekt stattfinden. Dies ist der Grund, warum Aufzählungen hilfreich erscheinen oder mindestens so hilfreich sind wieinstanceof
/is
checks.Das Besuchermuster kann dennoch nützlich sein. Mit anderen Worten, es gibt verschiedene Codierungsstile, die anstelle der riesigen Kette von verwendet werden können
instanceof
. Wenn Sie sich jedoch mit den verschiedenen Vor- und Nachteileninstanceof
befassen möchten , hätten Sie sich dafür entschieden, ein Codebeispiel aus der hässlichsten Kette des Projekts vorzustellen, anstatt über Aufzählungen zu streiten.Dies bedeutet nicht, dass Klassen und Vererbungshierarchien nicht nützlich sind. Ganz im Gegenteil. Während es keine polymorphen Verhaltensweisen gibt, die für jeden Deklarationstyp gelten (abgesehen von der Tatsache, dass jede Deklaration eine
Name
Eigenschaft haben muss ), gibt es viele polymorphe Verhaltensweisen, die von Geschwistern in der Nähe geteilt werden. Zum BeispielFunction
undProcedure
wahrscheinlich teilen sie einige Verhaltensweisen (beide sind aufrufbar und akzeptieren eine Liste typisierter Eingabeargumente) undPropertyGet
erben definitiv Verhaltensweisen vonFunction
(beide haben einReturnType
). Sie können entweder Aufzählungen oder Vererbungsprüfungen für die riesige Wenn-Dann-Sonst-Kette verwenden, aber das polymorphe Verhalten, wie fragmentiert es auch sein mag, muss immer noch in Klassen implementiert werden.Es gibt viele Online-Ratschläge gegen übermäßigen Gebrauch von
instanceof
/is
Schecks. Leistung ist nicht einer der Gründe. Vielmehr ist der Grund , den Programmierer zu verhindern , dass organisch geeignetes polymorphes Verhalten zu entdecken, als obinstanceof
/is
eine Krücke ist. In Ihrer Situation haben Sie jedoch keine andere Wahl, da diese Knoten nur sehr wenig gemeinsam haben.Hier nun einige konkrete Vorschläge.
Es gibt verschiedene Möglichkeiten, die Nicht-Blatt-Gruppierungen darzustellen.
Vergleichen Sie den folgenden Auszug aus Ihrem Originalcode ...
zu dieser modifizierten Version:
Diese modifizierte Version vermeidet die Zuweisung von Bits zu nicht konkreten Deklarationstypen. Stattdessen haben nicht konkrete Deklarationstypen (abstrakte Gruppierungen von Deklarationstypen) einfach Aufzählungswerte, die das bitweise Oder (Vereinigung der Bits) über alle untergeordneten Typen darstellen.
Es gibt eine Einschränkung: Wenn es einen abstrakten Deklarationstyp gibt, der ein einzelnes untergeordnetes Element hat, und wenn zwischen dem abstrakten (übergeordneten) und dem konkreten (untergeordneten) Element unterschieden werden muss, benötigt das abstrakte Element immer noch ein eigenes Element .
Eine Einschränkung , die spezifisch auf diese Frage ist: a
Property
ist zunächst eine Kennung (wenn man nur seinen Namen, ohne zu sehen , wie es im Code verwendet wird), aber es kann umwandeln inPropertyGet
/PropertyLet
/PropertySet
, sobald Sie sehen , wie es verwendet wird im Code. Mit anderen Worten, in verschiedenen Phasen des Parsens müssen Sie entweder einenProperty
Bezeichner als "dieser Name bezieht sich auf eine Eigenschaft" markieren und später in "diese Codezeile greift auf diese Eigenschaft auf eine bestimmte Weise zu" ändern.Um diese Einschränkung zu beheben, benötigen Sie möglicherweise zwei Sätze von Aufzählungen. Eine Aufzählung gibt an, was ein Name (Bezeichner) ist. Eine andere Aufzählung gibt an, was der Code versucht zu tun (z. B. den Körper von etwas zu deklarieren; zu versuchen, etwas auf eine bestimmte Weise zu verwenden).
Überlegen Sie, ob die Zusatzinformationen zu den einzelnen Aufzählungswerten stattdessen aus einem Array gelesen werden können.
Dieser Vorschlag schließt sich mit anderen Vorschlägen gegenseitig aus, da die Umwandlung von Zweierpotenzen in kleine nicht negative ganzzahlige Werte erforderlich ist.
Die Wartbarkeit wird problematisch sein.
Überprüfen Sie, ob eine Eins-zu-Eins-Zuordnung zwischen C # -Typen (Klassen in einer Vererbungshierarchie) und Ihren Aufzählungswerten besteht.
(Alternativ können Sie Ihre Enum-Werte optimieren, um eine Eins-zu-Eins-Zuordnung mit Typen sicherzustellen.)
In C # missbrauchen viele Bibliotheken die raffinierte
Type object.GetType()
Methode für gut oder schlecht.Überall, wo Sie die Aufzählung als Wert speichern, fragen Sie sich möglicherweise, ob Sie sie
Type
stattdessen als Wert speichern können .Um diesen Trick anzuwenden, können Sie zwei schreibgeschützte Hash-Tabellen initialisieren, nämlich:
Die letzte Rechtfertigung für diejenigen, die Klassen und Vererbungshierarchien vorschlagen ...
Sobald Sie sehen, dass die Aufzählungen eine Annäherung an die Vererbungshierarchie darstellen , gilt folgender Rat:
quelle
Ich finde Ihre Verwendung von Flaggen sehr intelligent, kreativ, elegant und potenziell am effizientesten. Ich habe auch keine Probleme, es zu lesen.
Flaggen sind ein Mittel zur Signalisierung, zur Qualifizierung. Wenn ich wissen will, ob etwas Obst ist, finde ich
thingy & Organic.Fruit! = 0
lesbarer als
thingy & (Organic.Apple | Organic.Orange | Organic.Pear)! = 0
Der eigentliche Sinn von Flag-Aufzählungen besteht darin, dass Sie mehrere Status kombinieren können. Sie haben dies nur nützlicher und lesbarer gemacht. Sie vermitteln das Konzept der Frucht in Ihrem Code, ich muss nicht selbst herausfinden, dass Apfel, Orange und Birne Frucht bedeuten.
Geben Sie diesem Kerl einige Schokoladenkuchenpunkte!
quelle
thingy is Fruit
ist besser lesbar als beides.