TL; DR Прокрутіть до самого низу.
Як я бачу, ви впроваджуєте нову мову поверх C #. Перерахунки, схоже, позначають тип ідентифікатора (або що-небудь, що має ім’я та що з’являється у вихідному коді нової мови), який, здається, застосовується до вузлів, які слід додати до дерева, що представляють програму.
У цій конкретній ситуації поліморфної поведінки між різними типами вузлів дуже мало. Іншими словами, хоча дерево має можливість містити вузли самих різних типів (варіантів), фактичне відвідування цих вузлів в основному вдасться до гігантської ланцюга "тоді ще" (або instanceof
/ is
перевірки). Ці гігантські перевірки, швидше за все, трапляться в багатьох місцях проекту. Це причина, чому перерахунки можуть здатися корисними або, принаймні, настільки ж корисними, як instanceof
і is
перевірки.
Модель відвідувачів все ще може бути корисною. Іншими словами, існують різні стилі кодування, які можна використовувати замість гігантського ланцюга instanceof
. Однак, якщо ви хочете обговорити різні переваги та недоліки, ви б вирішили продемонструвати приклад коду з найпотворнішої ланцюжки instanceof
проекту, а не посперечатися з приводу перерахунків.
Це не означає, що класи та ієрархія спадкування не корисні. Зовсім навпаки. Хоча немає жодної поліморфної поведінки, яка працює на кожному типі декларацій (окрім того, що кожна декларація повинна мати Name
властивість), є багато насичених поліморфних форм поведінки, якими поділяються сусідні побратими. Наприклад, Function
і, Procedure
ймовірно, поділиться деякою поведінкою (як виклику, так і прийняття списку введених вхідних аргументів), і PropertyGet
обов'язково успадкує поведінку від Function
(обидва мають a ReturnType
). Ви можете використовувати або перерахунки, або спадкові перевірки для гігантського ланцюга "тоді ще", але поліморфна поведінка, як би фрагментарна, все ж повинна бути реалізована в класах.
Існує багато порад в Інтернеті проти надмірного використання 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
ідентифікатор як "це ім'я відноситься до властивості", а пізніше змінити його на "цей рядок коду певним чином отримує доступ до цієї властивості".
Щоб вирішити цей застереження, вам можуть знадобитися два набори перерахунків; один перерахунок позначає, що таке ім’я (ідентифікатор); інша перерахунок позначає те, що намагається зробити код (наприклад, декларування тіла чогось; намагання певним чином використовувати щось).
Поміркуйте, чи можна замість зчитувати з масиву допоміжну інформацію про кожне значення перерахунку.
Ця пропозиція взаємовиключна з іншими пропозиціями, оскільки вимагає перетворення значень двох потужностей назад у малі невід’ємні цілі значення.
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 # (класи в ієрархії спадкування) та вашими значеннями enum.
(Крім того, ви можете налаштувати значення перерахунків, щоб забезпечити зіставлення один на один із типами.)
У 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
.