Насколько я понимаю, [Flag]
перечисления обычно используются для вещей, которые можно комбинировать, когда отдельные значения не являются взаимоисключающими .
Например:
[Flags]
public enum SomeAttributes
{
Foo = 1 << 0,
Bar = 1 << 1,
Baz = 1 << 2,
}
Если какое - либо SomeAttributes
значение может быть комбинацией Foo
, Bar
и Baz
.
В более сложном, реальном сценарии я использую перечисление для описания DeclarationType
:
[Flags]
public enum DeclarationType
{
Project = 1 << 0,
Module = 1 << 1,
ProceduralModule = 1 << 2 | Module,
ClassModule = 1 << 3 | Module,
UserForm = 1 << 4 | ClassModule,
Document = 1 << 5 | ClassModule,
ModuleOption = 1 << 6,
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,
Parameter = 1 << 14,
Variable = 1 << 15,
Control = 1 << 16 | Variable,
Constant = 1 << 17,
Enumeration = 1 << 18,
EnumerationMember = 1 << 19,
Event = 1 << 20,
UserDefinedType = 1 << 21,
UserDefinedTypeMember = 1 << 22,
LibraryFunction = 1 << 23 | Function,
LibraryProcedure = 1 << 24 | Procedure,
LineLabel = 1 << 25,
UnresolvedMember = 1 << 26,
BracketedExpression = 1 << 27,
ComAlias = 1 << 28
}
Очевидно, что данное Declaration
не может быть и a, Variable
и a LibraryProcedure
- два отдельных значения не могут быть объединены .. и это не так.
Хотя эти флаги чрезвычайно полезны (очень легко проверить, является ли данный DeclarationType
тип a Property
или a Module
), он кажется «неправильным», поскольку флаги на самом деле используются не для объединения значений, а для группировки их в «подтипы».
Поэтому мне сказали, что это неправильно использует флаги enum - этот ответ по существу говорит, что если у меня есть набор значений, применимый к яблокам, и другой набор, применимый к апельсинам, то мне нужен другой тип enum для яблок и другой тип для апельсинов - Проблема здесь в том, что мне нужно, чтобы все объявления имели общий интерфейс, и DeclarationType
чтобы они были представлены в базовом Declaration
классе: наличие PropertyType
enum не было бы полезным вообще.
Это небрежный / удивительный / оскорбительный дизайн? Если да, то как обычно решается эта проблема?
источник
DeclarationType
. Если я хочу определить,x
является ли это подтипом или нетy
, я, вероятно, захочу написать это какx.IsSubtypeOf(y)
, а не какx && y == y
.x.HasFlag(DeclarationType.Member)
делает ....HasFlag
вместоIsSubtypeOf
, тогда мне нужен какой-то другой способ выяснить, что он на самом деле означает «это подтип». Можно создать метод расширения, но как пользователь, что я бы найти хотя бы удивительно дляDeclarationType
просто быть структура , которая имеет вIsSubtypeOf
качестве реального способа.Ответы:
Это определенно злоупотребляет перечислениями и флагами! Это может сработать для вас, но любой, кто читает код, будет сильно сбит с толку.
Если я правильно понимаю, у вас есть иерархическая классификация объявлений. Это далеко к большому количеству информации , чтобы закодировать в одном перечислении. Но есть очевидная альтернатива: используйте классы и наследование! Так
Member
наследует отDeclarationType
,Property
наследует отMember
и так далее.Перечисления подходят для некоторых конкретных обстоятельств: если значение всегда является одним из ограниченного числа параметров, или если это любая комбинация ограниченного числа параметров (флагов). Любая информация, которая является более сложной или структурированной, чем эта, должна быть представлена с использованием объектов.
Изменить : в вашем "реальном сценарии" кажется, что есть несколько мест, где поведение выбирается в зависимости от значения перечисления. Это действительно антипаттерн, так как вы используете
switch
+enum
как «полиморфизм бедняков». Просто превратите значение enum в отдельные классы, инкапсулирующие поведение, специфичное для объявления, и ваш код станет намного чищеисточник
ParserRuleContext
типом сгенерированного класса, чем с enum. Этот код пытается получить позицию токена, куда вставитьAs {Type}
предложение в объявлении; этиParserRuleContext
классы-получатели генерируются Antr4 в соответствии с грамматикой, которая определяет правила синтаксического анализа - я на самом деле не контролирую [довольно поверхностную] иерархию наследования узлов синтаксического дерева, хотя я могу использовать их возможностиpartial
для реализации интерфейсов, например, для заставить их выставить какую-тоAsTypeClauseTokenPosition
собственность .. много работы.Я считаю этот подход достаточно легким для чтения и понимания. ИМХО, это не то, чтобы запутаться. При этом у меня есть некоторые опасения по поводу этого подхода:
Моя главная оговорка в том, что нет способа обеспечить это:
Пока вы не объявляете вышеуказанную комбинацию, этот код
Совершенно верно. И нет способа отловить эти ошибки во время компиляции.
Существует ограничение на количество информации, которую вы можете кодировать в битовых флагах, а что, 64 бита? На данный момент вы приближаетесь к размеру опасно,
int
и если этот список продолжает расти, вы можете в конечном итоге просто исчерпать себя ...Суть в том, что я думаю, что это правильный подход, но я бы не решился использовать его для больших / сложных иерархий флагов.
источник
int
тем не менее, поражение мощностей вызывает серьезную обеспокоенность.TL; DR Прокрутите до самого низа.
Из того, что я вижу, вы внедряете новый язык поверх C #. Перечисления, по-видимому, обозначают тип идентификатора (или всего, что имеет имя и появляется в исходном коде нового языка), которое, кажется, применяется к узлам, которые должны быть добавлены в древовидное представление программы.
В этой конкретной ситуации очень мало полиморфных поведений между различными типами узлов. Другими словами, в то время как это необходимо для дерева , чтобы иметь возможность содержать узлы самых разных типов (варианты), фактическое посещение этих узлов будет в основном прибегают к гигантскому , если-то-иначе цепь (или
instanceof
/is
проверки). Эти гигантские проверки, скорее всего, произойдут во многих местах проекта. По этой причине перечисления могут показаться полезными или, по крайней мере, такими же полезными, какinstanceof
/is
проверки.Шаблон посетителя может все еще быть полезным. Другими словами, существуют различные стили кодирования, которые можно использовать вместо гигантской цепочки
instanceof
. Однако, если вы хотите обсудить различные преимущества и недостатки, вы бы предпочли продемонстрировать пример кода из самой уродливой цепочкиinstanceof
проекта, а не спорить о перечислениях.Это не значит, что классы и иерархия наследования бесполезны. Наоборот. Хотя не существует полиморфных поведений, которые бы работали с каждым типом объявления (кроме того факта, что у каждого объявления должно быть
Name
свойство), существует множество богатых полиморфных поведений, общих для соседних братьев и сестер. Например,Function
и,Procedure
вероятно, разделяют некоторые поведения (оба являются вызываемыми и принимают список типизированных входных аргументов), иPropertyGet
определенно будут наследовать поведение отFunction
(оба имеют aReturnType
). Вы можете использовать перечисления или проверки наследования для гигантской цепочки if-then-else, но полиморфное поведение, пусть и фрагментированное, все же должно быть реализовано в классах.Есть много онлайн-советов против чрезмерного использования
instanceof
/is
проверок. Производительность не является одной из причин. Скорее, причина в том, чтобы не дать программисту органически обнаружить подходящие полиморфные поведения, как будтоinstanceof
/is
это опора. Но в вашей ситуации у вас нет другого выбора, так как у этих узлов очень мало общего.Теперь вот несколько конкретных предложений.
Есть несколько способов представления неконечных групп.
Сравните следующую выдержку из вашего исходного кода ...
к этой модифицированной версии:
Эта измененная версия избегает выделения битов для неконкретных типов объявлений. Вместо этого, неконкретные типы объявлений (абстрактные группы типов объявлений) просто имеют значения перечисления, которые являются побитовым (или объединением битов) для всех его дочерних элементов.
Существует предостережение: если существует абстрактный тип объявления, у которого есть один дочерний элемент, и если необходимо различать абстрактный (родительский) от конкретного (дочернего), то абстрактному по-прежнему потребуется свой собственный бит ,
Одно предостережение, характерное для этого вопроса: a
Property
изначально является идентификатором (когда вы просто видите его имя, не видя, как оно используется в коде), но оно может преобразоваться вPropertyGet
/PropertyLet
/,PropertySet
как только вы увидите, как оно используется в коде. Другими словами, на разных этапах анализа вам может понадобиться пометитьProperty
идентификатор как «это имя относится к свойству», а затем изменить его на «эта строка кода обращается к этому свойству определенным образом».Чтобы решить эту проблему, вам может понадобиться два набора перечислений; одно перечисление обозначает, что такое имя (идентификатор); другое перечисление обозначает, что код пытается сделать (например, объявлять тело чего-либо; пытаться использовать что-то определенным образом).
Подумайте, можно ли вместо этого считывать из массива вспомогательную информацию о каждом значении перечисления.
Это предложение является взаимоисключающим с другими предложениями, потому что оно требует преобразования значений степеней двух обратно в маленькие неотрицательные целые значения.
Ремонтопригодность будет проблематичной.
Проверьте, соответствует ли взаимно-однозначное соответствие между типами C # (классы в иерархии наследования) и значениями перечисления.
(В качестве альтернативы вы можете настроить значения перечисления, чтобы обеспечить однозначное сопоставление с типами.)
В C # многие библиотеки злоупотребляют изящным
Type object.GetType()
методом, хорошим или плохим.Везде, где вы храните enum как значение, вы можете спросить себя, можете ли вы сохранить
Type
вместо него значение.Чтобы использовать этот трюк, вы можете инициализировать две хеш-таблицы только для чтения, а именно:
Последнее оправдание для тех, кто предлагает классы и иерархию наследования ...
Как только вы увидите, что перечисления являются приближением к иерархии наследования , действует следующий совет:
источник
Я считаю, что вы используете флаги действительно умно, креативно, элегантно и потенциально наиболее эффективно. У меня тоже нет проблем с чтением.
Флаги являются средством сигнализации состояния, уточнения. Если я хочу знать, если что-то фрукт, я нахожу
штука и органика. Фрукт! = 0
более читабельным, чем
thingy & (Organic.Apple | Organic.Orange | Organic.Pear)! = 0
Суть перечисления флагов заключается в том, чтобы позволить вам объединять несколько состояний. Вы просто сделали это более полезным и читабельным. Вы передаете концепцию фруктов в своем коде, мне не нужно понимать, что яблоко, апельсин и груша означают фрукты.
Дайте этому парню несколько очков брауни!
источник
thingy is Fruit
более читабельным, чем любой, хотя.