TL; DR滚动到最底部。
据我所知,您正在C#之上实现一种新语言。枚举似乎表示标识符的类型(或任何具有名称且出现在新语言的源代码中的名称),该标识符似乎适用于要添加到程序树表示形式的节点。
在这种特殊情况下,不同类型的节点之间几乎没有多态行为。换句话说,虽然树必须能够包含非常不同类型(变量)的节点,但是这些节点的实际访问基本上将诉诸于巨大的if-then-else链(或instanceof
/ is
checks)。这些巨大的检查可能会在整个项目的许多不同地方进行。这就是为什么枚举似乎有用,或者它们至少和instanceof
/ is
check 一样有用的原因。
访客模式可能仍然有用。换句话说,可以使用多种编码样式来代替的巨型链instanceof
。但是,如果您想讨论各种优点和缺点,则可以选择展示instanceof
项目中最丑陋的链中的代码示例,而不是为枚举而烦恼。
这并不是说类和继承层次结构没有用。恰恰相反。尽管没有任何一种适用于每种声明类型的多态行为(除了每个声明都必须具有Name
属性的事实),但附近的兄弟姐妹共享了许多丰富的多态行为。例如,Function
并且Procedure
可能共享一些行为(都可以被调用并接受一连串的输入参数),并且PropertyGet
肯定会从Function
(都具有ReturnType
)继承行为。您可以对大型if-then-else链使用枚举或继承检查,但是多态行为(无论多么零碎)仍必须在类中实现。
有很多关于过度使用instanceof
/ is
支票的在线建议。性能不是原因之一。相反,其原因是为了防止程序员自然地发现合适的多态行为,就像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
}
此修改后的版本避免为非具体的声明类型分配位。相反,非具体的声明类型(声明类型的抽象分组)仅具有枚举值,该枚举值在其所有子代中都是按位或(位的联合)。
需要注意的是:如果有一个具有单个子代的抽象声明类型,并且有必要将抽象的(父代)与具体的一个(子代)区分开,则抽象的那一类仍然需要自己的位。
一个需要注意的是具体到这样一个问题:一个Property
是最初的标识符(当你只是看到它的名字,没有看到它是如何在代码中使用),但它可能会蜕变成PropertyGet
/ PropertyLet
/ PropertySet
只要你看到它正在使用在代码中。换句话说,在解析的不同阶段,您可能需要将Property
标识符标记为“此名称引用属性”,然后将其更改为“此代码行以某种方式访问此属性”。
要解决此警告,您可能需要两组枚举。一个枚举表示名称(标识符)是什么;另一个枚举表示代码正在尝试执行的操作(例如,声明某些内容的主体;尝试以某种方式使用某些内容)。
考虑是否可以从数组中读取有关每个枚举值的辅助信息。
该建议与其他建议互斥,因为它需要将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#类型(继承层次结构中的类)和您的枚举值之间是否一对一映射。
(或者,您可以调整枚举值以确保与类型的一对一映射。)
在C#中,Type object.GetType()
无论好坏,许多库都滥用这种漂亮的方法。
在将枚举存储为值的任何地方,您都可能会问自己是否可以将其存储Type
为值。
要使用此技巧,您可以初始化两个只读哈希表,即:
// 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
。