Оператор переключения нескольких переменных в c # - PullRequest
21 голосов
/ 01 ноября 2011

Я хотел бы использовать оператор switch, который принимает несколько переменных и выглядит следующим образом:

switch (intVal1, strVal2, boolVal3)
{
   case 1, "hello", false:
      break;
   case 2, "world", false:
      break;
   case 2, "hello", false:

   etc ....
}

Есть ли способ сделать что-то подобное в C #?(Я не хочу использовать вложенные операторы switch по понятным причинам).

Ответы [ 10 ]

17 голосов
/ 26 мая 2018

Вы можете сделать это в C # 7 и выше с ключевым словом when:

switch (intVal1)
{
    case 1 when strVal2 == "hello" && boolVal3 == false:
        break;
    case 2 when strVal2 == "world" && boolVal3 == false:
        break;
    case 2 when strVal2 == "hello" && boolVal3 == false:
        break;
}
12 голосов
/ 01 ноября 2011

Давайте посмотрим на это по-другому.Если у вас есть:

  • Очень конкретные комбинации, которые вы хотите проверить;
  • Нет сравнений;
  • Обработчик по умолчанию для каждогонесовпадающий регистр;
  • Все типы примитивов / значений (int, bool, string и т. д.)

Тогда вы можете использовать взглядвместо таблицы , которая имеет скорость выполнения, аналогичную скорости выполнения оператора switch, но не настолько эффективна (так как она требует вычисления хэшей).Тем не менее, это, вероятно, достаточно хорошо.И это дает вам возможность называть случаи, чтобы сделать этот комбинаторный взрыв немного менее запутанным и не поддерживаемым.

Пример кода:

private static readonly Tuple<int, int, bool> NameOfCase1 = 
    Tuple.Create(1, 1, false);
private static readonly Tuple<int, int, bool> NameOfCase2 =
    Tuple.Create(2, 1, false);
private static readonly Tuple<int, int, bool> NameOfCase3 =
    Tuple.Create(2, 2, false);

private static readonly Dictionary<Tuple<int, int, bool>, string> Results =
    new Dictionary<Tuple<int, int, bool>, string>
{
    { NameOfCase1, "Result 1" },
    { NameOfCase2, "Result 2" },
    { NameOfCase3, "Result 3" }
};

public string GetResultForValues(int x, int y, bool b)
{
    const string defaultResult = "Unknown";
    var lookupValue = Tuple.Create(x, y, b);
    string result;
    Results.TryGetValue(lookupValue, out result);
    return defaultResult;
}

Если вам действительно нужно выполнить функцию или методтогда для каждого случая вы можете использовать тип результата (значение словаря) Action<T> или Func<T>.

Обратите внимание, что я использую Tuple<T1,T2,T3> здесь, потому что он уже имеет всю логику хеш-кодавстроенный. Синтаксис немного неудобен в C #, но если вы хотите, вы можете реализовать свой собственный класс поиска и просто переопределить Equals и GetHashCode.

10 голосов
/ 01 ноября 2011

Нет встроенной функциональности для этого в C #, и я не знаю ни о какой библиотеке, чтобы сделать это.

Вот альтернативный подход, использующий Tuple и методы расширения:

using System;

static class CompareTuple {
    public static bool Compare<T1, T2, T3>(this Tuple<T1, T2, T3> value, T1 v1, T2 v2, T3 v3) {
        return value.Item1.Equals(v1) && value.Item2.Equals(v2) && value.Item3.Equals(v3); 
    }
}

class Program {
    static void Main(string[] args) {
        var t = new Tuple<int, int, bool>(1, 2, false);
        if (t.Compare(1, 1, false)) {
            // 1st case
        } else if (t.Compare(1, 2, false)) {
            // 2nd case
        } else { 
            // default
        }
    }
}

Это в основном ничего не делает, кроме предоставления удобного синтаксиса для проверки нескольких значений и использования нескольких if вместо переключателя.

6 голосов
/ 01 ноября 2011

Мой совершенно сумасшедший взгляд на это:

class Program
{
    static void Main(string[] args)
    {
        var i = 1;
        var j = 34;
        var k = true;
        Match(i, j, k).
            With(1, 2, false).Do(() => Console.WriteLine("1, 2, 3")).
            With(1, 34, false).Do(() => Console.WriteLine("1, 34, false")).
            With(x => i > 0, x => x < 100, x => x == true).Do(() => Console.WriteLine("1, 34, true"));

    }

    static Matcher<T1, T2, T3> Match<T1, T2, T3>(T1 t1, T2 t2, T3 t3)
    {
        return new Matcher<T1, T2, T3>(t1, t2, t3);
    }
}

public class Matcher<T1, T2, T3>
{
    private readonly object[] values;

    public object[] Values
    {
        get { return values; }
    }

    public Matcher(T1 t1, T2 t2, T3 t3)
    {
        values = new object[] { t1, t2, t3 };
    }

    public Match<T1, T2, T3> With(T1 t1, T2 t2, T3 t3)
    {
        return new Match<T1, T2, T3>(this, new object[] { t1, t2, t3 });
    }

    public Match<T1, T2, T3> With(Func<T1, bool> t1, Func<T2, bool> t2, Func<T3, bool> t3)
    {
        return new Match<T1, T2, T3>(this, t1, t2, t3);
    }
}

public class Match<T1, T2, T3>
{
    private readonly Matcher<T1, T2, T3> matcher;
    private readonly object[] matchedValues;
    private readonly Func<object[], bool> matcherF; 

    public Match(Matcher<T1, T2, T3> matcher, object[] matchedValues)
    {
        this.matcher = matcher;
        this.matchedValues = matchedValues;
    }

    public Match(Matcher<T1, T2, T3> matcher, Func<T1, bool> t1, Func<T2, bool> t2, Func<T3, bool> t3)
    {
        this.matcher = matcher;


        matcherF = objects => t1((T1)objects[0]) && t2((T2)objects[1]) && t3((T3)objects[2]);
    }

    public Matcher<T1, T2, T3> Do(Action a)
    {
        if(matcherF != null && matcherF(matcher.Values) || matcher.Values.SequenceEqual(matchedValues))
            a();

        return matcher;
    }
}
4 голосов
/ 01 ноября 2011

Вы можете преобразовать в строку:

switch (intVal1.ToString() + strVal2 + boolVal3.ToString())
{
   case "1helloFalse":
      break;
   case "2worldFalse":
      break;
   case "2helloFalse":

   etc ....
}

Я думаю, что вопрос, который встает в игру, заключается в том, есть ли лучший способ определить логику. Например, допустим, вы пытаетесь выяснить, кто знает супермена. Мы могли бы сделать проверку следующим образом:

switch (first + last)
{
   case "ClarkKent":
   case "LoisLane":
      // YES
      break;
   default;
      // Sadly, no
      break;
}

Но что происходит, когда у тебя появляется другой парень по имени Кларк Кент? Неужели у вас не может быть другого значения, на основе которого вы определяете эту логику, например, bool KnowsSuperman?

Идея состоит в том, что оператор switch используется для определения логики, основанной на одном наборе вариантов. Если есть несколько значений, которые вы пытаетесь отключить, тогда логику может быть безумно сложно поддерживать в дальнейшем.

Другим примером может быть, если вам нужно сгруппировать людей в несколько групп и выполнить некоторую логику в зависимости от группы, в которой они находятся. Вы можете написать код, чтобы сказать, что вы Боб, Джефф, Джим или Салли Вы находитесь в группе А, но что, если вам нужно добавить кого-то еще в группу А? Вам придется изменить код. Вместо этого вы можете создать дополнительное свойство с именем Group, которое может быть перечислением или строкой, которые вы можете использовать, чтобы указать, в какую группу входит кто-то.

2 голосов
/ 25 мая 2018

Обновление для 2018. Начиная с C # 7.0, Microsoft ввела предложение «когда» для коммутаторов, что позволяет эффективно расширять регистры коммутаторов дополнительными условиями.

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/switch#the-case-statement-and-the-when-clause

1 голос
/ 01 ноября 2011

В соответствии со спецификацией языка C # выражение switch должно принимать одно из следующих значений: sbyte, byte, sbyte, byte, short, ushort, int, uint, long, ulong, char, string или enum-типа .Это означает, что вы не можете включить Tuple или другие типы более высокого порядка.

Вы можете попытаться упаковать значения вместе, предполагая, что есть место.Например, предположим, что каждое из целых чисел гарантированно находится в диапазоне 0..9.

switch (intVal1 * 100 + intVal2 * 10 + (boolVal3 ? 1 : 0))
{
case 100: /* intVal1 = 1, intVal2 = 0, boolVal3 = false */ ... break;
case 831: /* intVal1 = 8, intVal2 = 3, boolVal3 = true */ ... break;
}
0 голосов
/ 01 ноября 2011

Я делаю такие вещи со списками или массивами. Если вы можете перечислить возможные условия (что вы, очевидно, можете, если хотите сделать многозначное переключение), то создайте таблицу поиска с ключом из нескольких частей и Action или Func<T> в качестве значения.

Простая версия будет использовать Dictionary:

class LookupKey: IComparable<LookupKey>
{
    public int IntValue1 { get; private set; }
    public int IntValue2 { get; private set; }
    public bool BoolValue1 { get; private set; }
    public LookupKey(int i1, int i2, bool b1)
    {
        // assign values here
    }
    public int Compare(LookupKey k1, LookupKey k2)
    {
        return k1.IntValue1 == k2.IntValue1 &&
               k1.IntValue2 == k2.IntValue2 &&
               k1.BoolValue1 == k2.BoolValue1;
    }
    public int GetHashCode()
    {
        return (19 * IntValue1) + (1000003 * IntValue2) + (BoolValue1) ? 0 : 100000073;
    }
    // need to override Equals
}

А твой словарь:

static readonly Dictionary<LookupKey, Action<object>> LookupTable;

Затем вы можете заполнить словарь при запуске, и тогда поиск станет простым делом:

Action<object> MethodToCall;
if (LookupTable.TryGetValue(new LookupKey(i1, i2, b1), out MethodToCall)
    MethodToCall(theData);
else
    // default action if no match

Это немного кода для настройки, но он очень быстр в исполнении.

0 голосов
/ 01 ноября 2011
if (a == 1 && b == 1) {}
else if (a == 1 && b == 2) {}
else if (a == 2 && b ==2) {}
0 голосов
/ 01 ноября 2011

Насколько я знаю, вы не можете сделать это в C #.

Но вы можете сделать это из MSDN:

В следующем примере показано, что допускается переход от одной метки к другой.для пустых этикеток:

 switch(n) 
        {
            case 1:
            case 2: 
            case 3: 
                Console.WriteLine("It's 1, 2, or 3.");
                break; 
        default: 
            Console.WriteLine("Not sure what it is.");
            break; 
        }
...