Как выполнять итерацию по типу перечисления при пропуске некоторых значений?

Ключевой частью моего вопроса является пропуск. Я планирую использовать тип перечисления, который содержит около 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));
}