Как выполнять итерацию по типу перечисления при пропуске некоторых значений?
Ключевой частью моего вопроса является пропуск. Я планирую использовать тип перечисления, который содержит около 20 элементов. Я хочу повторить этот набор, но каждый раз нужно пропускать элемент или два. Что пропустить известно заранее. Сравнимым примером является тип перечисления, который состоит из всех букв алфавита, а при повторении я хочу пропустить все гласные.
Как я должен кодировать итерацию элегантным/эффективным способом? Должен ли я сделать отдельный набор элементов, состоящих из гласных? У меня нет кода для показа, потому что я просто думаю о проблеме.
Ответы
Ответ 1
var query = Enum.GetValues(typeof(MyEnum))
.Cast<MyEnum>()
.Except(new MyEnum[] { MyEnum.A, MyEnum.E });
foreach (MyEnum item in query) {
...
}
Вам нужно бросить, чтобы получить магию LINQ. Только Except
этого не сделает.
UPDATE:
У меня появилась другая идея. Вы можете определить перечисление с помощью FlagsAttribute
и определить регулярные значения в виде степеней 2, что наиболее легко достигается с помощью оператора побитового сдвига влево <<
. Начиная с С# 7.0, вы также можете использовать бинарные литералы, такие как 0b_0000_0000_0010_0000
. Затем можно комбинировать существующие значения для формирования новых значений.
[Flags]
enum MyEnum
{
None = 0,
A = 1 << 0,
B = 1 << 1,
C = 1 << 2,
D = 1 << 3,
E = 1 << 4,
...
X = 1 << 23,
Y = 1 << 24,
Z = 1 << 25,
Vowels = A | E | I | O | U
}
Теперь вы можете сформулировать запрос, подобный этому
IEnumerable<MyEnum> query = Enum.GetValues(typeof(MyEnum))
.Cast<MyEnum>()
.Where(x => (x & MyEnum.Vowels) == MyEnum.None);
foreach (MyEnum item in query) {
...
}
Преимущество перед первым решением заключается в том, что вы можете выполнить тест с помощью одной побитовой операции AND.
Вы можете определить до 32 степеней два. Если вам нужно больше, вы можете определить базовый тип перечисления как long
и использовать до 64 значений флага (плюс комбинации существующих значений флага).
[Flags]
enum MyEnum : long
{
...
}
Ответ 2
Я сделал бы отдельный набор элементов, состоящий из гласных, а затем возьмем разницу между двумя наборами, используя LINQ.
int[] vowels = {Letters.A, Letters.E, Letters.I, Letters.O, Letters.U};
IEnumerable<int> consonant = Enum.GetValues(typeof(Letters)).Except(vowels);
foreach (int consonant in consonants)
{
// Do something with each consonant
}
Ответ 3
Я бы, скорее всего, использовал LINQ - используйте Enum.GetValues
(или используйте Unconstrained Melody - универсальную библиотеку enum/delegate, защищенную типом я написал), чтобы получить все значения, а затем выразить, какие значения сохранить/пропустить с помощью предложения Where
.
Если вы пропускаете только определенные значения, то HashSet
или что-то подобное могут быть полезны (не стоит того, если вы только пропустите их, конечно) - если вы пропустите на основе условия, вызывается полномасштабный предикат.
Например:
public static IEnumerable<T> AllBut(T skipped) where T : struct
{
IEqualityComparer<T> comparer = EqualityComparer<T>.Default;
return AllBut<T>(t => !comparer.Equals(skipped, t));
}
public static IEnumerable<T> AllBut(Func<T, bool> skipPredicate) where T : struct
{
IEqualityComparer<T> comparer = EqualityComparer<T>.Default;
return Enum.GetValues(typeof(T))
.Cast<T>()
.Where(t => skipPredicate(t));
}