TL; DR Прокрутите до самого низа.
Из того, что я вижу, вы внедряете новый язык поверх C #. Перечисления, по-видимому, обозначают тип идентификатора (или всего, что имеет имя и появляется в исходном коде нового языка), которое, кажется, применяется к узлам, которые должны быть добавлены в древовидное представление программы.
В этой конкретной ситуации очень мало полиморфных поведений между различными типами узлов. Другими словами, в то время как это необходимо для дерева , чтобы иметь возможность содержать узлы самых разных типов (варианты), фактическое посещение этих узлов будет в основном прибегают к гигантскому , если-то-иначе цепь (или instanceof
/ is
проверки). Эти гигантские проверки, скорее всего, произойдут во многих местах проекта. По этой причине перечисления могут показаться полезными или, по крайней мере, такими же полезными, как instanceof
/ is
проверки.
Шаблон посетителя может все еще быть полезным. Другими словами, существуют различные стили кодирования, которые можно использовать вместо гигантской цепочки instanceof
. Однако, если вы хотите обсудить различные преимущества и недостатки, вы бы предпочли продемонстрировать пример кода из самой уродливой цепочки instanceof
проекта, а не спорить о перечислениях.
Это не значит, что классы и иерархия наследования бесполезны. Наоборот. Хотя не существует полиморфных поведений, которые бы работали с каждым типом объявления (кроме того факта, что у каждого объявления должно быть Name
свойство), существует множество богатых полиморфных поведений, общих для соседних братьев и сестер. Например, Function
и, Procedure
вероятно, разделяют некоторые поведения (оба являются вызываемыми и принимают список типизированных входных аргументов), и PropertyGet
определенно будут наследовать поведение от Function
(оба имеют a 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
}
Эта измененная версия избегает выделения битов для неконкретных типов объявлений. Вместо этого, неконкретные типы объявлений (абстрактные группы типов объявлений) просто имеют значения перечисления, которые являются побитовым (или объединением битов) для всех его дочерних элементов.
Существует предостережение: если существует абстрактный тип объявления, у которого есть один дочерний элемент, и если необходимо различать абстрактный (родительский) от конкретного (дочернего), то абстрактному по-прежнему потребуется свой собственный бит ,
Одно предостережение, характерное для этого вопроса: a Property
изначально является идентификатором (когда вы просто видите его имя, не видя, как оно используется в коде), но оно может преобразоваться в PropertyGet
/ PropertyLet
/, PropertySet
как только вы увидите, как оно используется в коде. Другими словами, на разных этапах анализа вам может понадобиться пометить Property
идентификатор как «это имя относится к свойству», а затем изменить его на «эта строка кода обращается к этому свойству определенным образом».
Чтобы решить эту проблему, вам может понадобиться два набора перечислений; одно перечисление обозначает, что такое имя (идентификатор); другое перечисление обозначает, что код пытается сделать (например, объявлять тело чего-либо; пытаться использовать что-то определенным образом).
Подумайте, можно ли вместо этого считывать из массива вспомогательную информацию о каждом значении перечисления.
Это предложение является взаимоисключающим с другими предложениями, потому что оно требует преобразования значений степеней двух обратно в маленькие неотрицательные целые значения.
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()
методом, хорошим или плохим.
Везде, где вы храните enum как значение, вы можете спросить себя, можете ли вы сохранить 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
.