TL; DR一番下までスクロールします。
私が見るところから、あなたはC#の上に新しい言語を実装しています。列挙型は、識別子のタイプ(または名前を持ち、新しい言語のソースコードに表示されるもの)を示しているようです。これは、プログラムのツリー表現に追加されるノードに適用されるようです。
この特定の状況では、異なるタイプのノード間で多態的な動作はほとんどありません。言い換えると、ツリーには非常に異なるタイプのノード(バリアント)を含めることができる必要がありますが、これらのノードの実際の訪問は基本的に巨大なif-then-elseチェーン(またはinstanceof
/ is
チェック)に頼ります。これらの巨大なチェックは、プロジェクト全体のさまざまな場所で行われる可能性があります。これが、列挙型が役立つと思われる理由、または少なくともinstanceof
/ is
チェックと同じくらい役立つ理由です。
訪問者パターンはまだ有用かもしれません。つまり、の巨大なチェーンの代わりに使用できるさまざまなコーディングスタイルがありinstanceof
ます。ただし、さまざまな長所と短所について議論したい場合instanceof
は、列挙型について口論するのではなく、プロジェクト内で最もuいチェーンのコード例を紹介することを選択します。
これは、クラスと継承階層が役に立たないと言っているわけではありません。まったく逆です。すべての宣言タイプで機能するポリモーフィックな動作はありませんが(すべての宣言にName
プロパティが必要であるという事実を除く)、近くの兄弟で共有される豊富なポリモーフィックな動作がたくさんあります。例えば、Function
そしてProcedure
おそらくいくつかの行動を(両方の呼び出し可能であることと、型指定された入力引数のリストを受け入れる)を共有し、そしてPropertyGet
意志から間違いなく継承行動Function
(両方とも持ちますReturnType
)。巨大なif-then-elseチェーンに対して列挙または継承チェックを使用できますが、断片化されたポリモーフィックな動作はクラスで実装する必要があります。
instanceof
/ is
チェックの使いすぎに対する多くのオンラインアドバイスがあります。パフォーマンスが理由の1つではありません。むしろ、その理由は、プログラマーがinstanceof
/ is
が松葉杖であるかのように、適切なポリモーフィックな動作を有機的に発見するのを防ぐためです。ただし、これらのノードにはほとんど共通点がないため、状況によっては他に選択肢はありません。
次に、具体的な提案を示します。
葉以外のグループ化を表す方法はいくつかあります。
元のコードの次の抜粋を比較してください...
[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,
}
この修正版へ:
[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
}
この変更されたバージョンでは、非具象宣言型にビットを割り当てることを避けています。代わりに、非具象宣言型(宣言型の抽象的なグループ化)には、すべての子のビット単位の論理和(ビットの結合)である列挙値があります。
警告があります:単一の子を持つ抽象宣言型があり、抽象型(親)と具象型(子)を区別する必要がある場合、抽象型にはまだ独自のビットが必要です。
この質問に固有の1つの注意:a Property
は最初は識別子です(コードでの使用方法が表示されずに名前が表示されるだけです)が、使用方法が表示されるとすぐにPropertyGet
/ PropertyLet
/に変換さPropertySet
れる場合がありますコード内。つまり、解析のさまざまな段階で、Property
識別子を「この名前がプロパティを参照している」とマークし、後で「このコード行が特定の方法でこのプロパティにアクセスしている」と変更する必要があります。
この警告を解決するには、2セットの列挙が必要になる場合があります。1つの列挙は、名前(識別子)が何であるかを示します。別の列挙型は、コードが何をしようとしているのかを示します(たとえば、何かの本体を宣言する、特定の方法で何かを使用しようとする)。
代わりに、各列挙値に関する補助情報を配列から読み取ることができるかどうかを検討してください。
この提案は、2のべき乗値を小さな非負の整数値に変換する必要があるため、他の提案と相互に排他的です。
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), ...
保守性には問題があります。
C#型(継承階層のクラス)と列挙値の間の1対1マッピングかどうかを確認します。
(または、列挙値を微調整して、型と1対1のマッピングを確保することもできます。)
C#では、多くのライブラリが気の利いたType object.GetType()
方法を悪用して悪用します。
列挙型を値として保存しているところはどこでもType
、代わりに値として保存できるかどうかを自問するかもしれません。
このトリックを使用するには、次の2つの読み取り専用ハッシュテーブルを初期化します。
// 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
...
};
クラスと継承階層を提案している人の最終的な証明...
列挙型が継承階層の近似値であることがわかると、次のアドバイスが成り立ちます。
- 最初に継承階層を設計(または改善)し、
- その後、戻って列挙を設計し、その継承階層を概算します。
DeclarationType
。のx
サブタイプであるかどうかを判断したい場合は、y
おそらくとしてx.IsSubtypeOf(y)
ではなくとして記述したいと思いx && y == y
ます。