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 wie instanceof
/ 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 Nachteilen instanceof
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 Beispiel Function
und Procedure
wahrscheinlich teilen sie einige Verhaltensweisen (beide sind aufrufbar und akzeptieren eine Liste typisierter Eingabeargumente) und PropertyGet
erben definitiv Verhaltensweisen von Function
(beide haben ein ReturnType
). 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 ob instanceof
/ 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 ...
[Flags]
public enum DeclarationType
{
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,
LibraryFunction = 1 << 23 | Function,
LibraryProcedure = 1 << 24 | Procedure,
}
zu dieser modifizierten Version:
[Flags]
public enum DeclarationType
{
Nothing = 0, // to facilitate bit testing
// Let's assume Member is not a concrete thing,
// which means it doesn't need its own bit
/* Member = 1 << 7, */
// Procedure and Function are concrete things; meanwhile
// they can still have sub-types.
Procedure = 1 << 8,
Function = 1 << 9,
Property = 1 << 10,
PropertyGet = 1 << 11,
PropertyLet = 1 << 12,
PropertySet = 1 << 13,
LibraryFunction = 1 << 23,
LibraryProcedure = 1 << 24,
// new
Procedures = Procedure | PropertyLet | PropertySet | LibraryProcedure,
Functions = Function | PropertyGet | LibraryFunction,
Properties = PropertyGet | PropertyLet | PropertySet,
Members = Procedures | Functions | Properties,
LibraryMembers = LibraryFunction | LibraryProcedure
}
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 in PropertyGet
/ PropertyLet
/ PropertySet
, sobald Sie sehen , wie es verwendet wird im Code. Mit anderen Worten, in verschiedenen Phasen des Parsens müssen Sie entweder einen Property
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.
public enum DeclarationType
{
Procedure = 8,
Function = 9,
Property = 10,
PropertyGet = 11,
PropertyLet = 12,
PropertySet = 13,
LibraryFunction = 23,
LibraryProcedure = 24,
}
static readonly bool[] DeclarationTypeIsMember = new bool[32]
{
?, ?, ?, ?, ?, ?, ?, ?, // bit[0] ... bit[7]
true, true, true, true, true, true, ?, ?, // bit[8] ... bit[15]
?, ?, ?, ?, ?, ?, ?, true, // bit[16] ... bit[23]
true, ... // bit[24] ...
}
static bool IsMember(DeclarationType dt)
{
int intValue = (int)dt;
return (intValue < 0 || intValue >= 32) ? false : DeclarationTypeIsMember[intValue];
// you can also throw an exception if the enum is outside range.
}
// likewise for IsFunction(dt), IsProcedure(dt), IsProperty(dt), ...
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:
// For disambiguation, I'll assume that the actual
// (behavior-implementing) classes are under the
// "Lang" namespace.
static readonly Dictionary<Type, DeclarationType> TypeToDeclEnum = ...
{
{ typeof(Lang.Procedure), DeclarationType.Procedure },
{ typeof(Lang.Function), DeclarationType.Function },
{ typeof(Lang.Property), DeclarationType.Property },
...
};
static readonly Dictionary<DeclarationType, Type> DeclEnumToType = ...
{
// same as the first dictionary;
// just swap the key and the value
...
};
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:
- Entwerfen (oder verbessern) Sie zuerst Ihre Vererbungshierarchie.
- Dann gehen Sie zurück und entwerfen Sie Ihre Aufzählungen, um diese Vererbungshierarchie zu approximieren.
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
.