Оператор switch с несколькими переменными в C#

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

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

   etc ....
}

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

На этот вопрос ответила команда разработчиков .net, реализовав именно этот страх: ">Многопеременная инструкция switch в C#


person BanditoBunny    schedule 01.11.2011    source источник


Ответы (10)


да. Он поддерживается начиная с .NET 4.7 и C# 8. Синтаксис почти такой же, как вы упомянули, но с некоторыми скобками (см. шаблоны кортежей).

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

Если вы хотите переключиться и вернуть значение, существует синтаксис выражения переключения. Вот пример; обратите внимание на использование _ для случая по умолчанию:

string result = (intVal1, strVal2, boolVal3) switch
{
    (1, "hello", false) => "Combination1",
    (2, "world", false) => "Combination2",
    (2, "hello", false) => "Combination3",
    _ => "Default"
};

Вот более наглядный пример (игра камень, ножницы, бумага) из статьи MSDN, ссылка на которую приведена выше:

public static string RockPaperScissors(string first, string second)
    => (first, second) switch
    {
        ("rock", "paper") => "rock is covered by paper. Paper wins.",
        ("rock", "scissors") => "rock breaks scissors. Rock wins.",
        ("paper", "rock") => "paper covers rock. Paper wins.",
        ("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
        ("scissors", "rock") => "scissors is broken by rock. Rock wins.",
        ("scissors", "paper") => "scissors cuts paper. Scissors wins.",
        (_, _) => "tie"
    };
person Stephen Kennedy    schedule 01.10.2019
comment
Это более или менее то, что я искал в то время, спасибо за обновление. - person BanditoBunny; 22.10.2019

Вы можете сделать это в 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;
}
person Stephen Kennedy    schedule 26.05.2018
comment
это, безусловно, менее сложное и более читаемое решение, чем большинство представленных здесь, когда у вас не слишком много случаев, благодаря этой новой языковой функции. - person Pac0; 14.06.2018
comment
Верно. Конечно, его можно было бы сделать более кратким, заменив boolVal3 == false на !boolVal3 (при условии, что это логическое значение, а не логическое значение, допускающее значение NULL). - person Stephen Kennedy; 02.10.2018

В С# нет (не было) встроенных функций для этого, и я не знаю ни одной библиотеки для этого.

Вот альтернативный подход с использованием методов 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 вместо switch.

person Paolo Tedesco    schedule 01.11.2011
comment
Для читателей: теперь он поддерживается из коробки - person Vimes; 09.12.2020
comment
Этот ответ уже довольно старый :) - person Paolo Tedesco; 10.12.2020
comment
Я понимаю, не критикуя ???? - person Vimes; 10.12.2020
comment
Может быть, мы должны иметь возможность помечать эти страницы как устаревшие, все еще видимые, но они должны выглядеть совсем иначе, чем обычный вопрос, например, большое предупреждение вверху или что-то в этом роде. Смущает появление всех этих старых ответов на старые вопросы, С# несколько изменился за 10 лет. - person Gavin Williams; 09.04.2021

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

  • Очень конкретные комбинации, которые вы хотите проверить;
  • Никаких сравнений;
  • Обработчик по умолчанию для каждого случая несоответствия;
  • Все типы примитивов/значений (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.

person Aaronaught    schedule 01.11.2011
comment
+1. Вы выбрали Tuple, а я выбрал отдельный класс поиска. Думаю, мне больше нравится твоя идея. - person Jim Mischel; 01.11.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;
    }
}
person Anton Gogolev    schedule 01.11.2011
comment
Интересная квазифункциональная версия, хотя и небезопасная для типов (и потенциально небезопасная для сравнения). Я мог бы использовать IComparable или IEquatable вместо object. В лучшей версии будут использоваться дженерики. - person Aaronaught; 01.11.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, которое может быть перечислением или строкой, которую вы можете использовать, чтобы указать, в какой группе кто-то находится.

person Mark Synowiec    schedule 01.11.2011

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

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

person Hastaroth    schedule 25.05.2018

В соответствии со спецификацией языка C# выражение оператора switch должно разрешаться в одно из sbyte, byte, sbyte, byte, short, ushort, int, uint, long, ulong, char, string или тип перечисления. Это означает, что вы не можете включить 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;
}
person Raymond Chen    schedule 01.11.2011
comment
Я смотрел на это больше как на головоломку. - person Raymond Chen; 01.11.2011
comment
Если вы создаете перечисление для значений, это ОЧЕНЬ читабельно. Плюс, как только я закончил читать вопрос, первой мыслью в моей голове было использование побитовых операторов и флагов. - person Sivvy; 01.11.2011
comment
Эту возможность я тоже рассматривал, однако это нехорошо :( - person BanditoBunny; 01.11.2011
comment
Особенно, когда вы меняете вопрос, чтобы теперь включить строку в качестве второго параметра, когда изначально он был целым числом. - person Sivvy; 01.11.2011
comment
Если это все перечисления, вы можете написать case более читаемым способом, используя +, так как он все равно будет оцениваться во время компиляции. Например. для перечислений Day и Month: Day d = Day.Wednesday; Month m = Month.February; switch ((int)d + 8*(int)m) { case (int)Day.Monday + 7*(int)Month.January: ... break; case (int)Day.Wednesday + 7*(int)Month.February: ... break; ...} - person Kjara; 27.11.2017

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

Но вы можете сделать это из 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; 
        }
person JonH    schedule 01.11.2011
comment
Привет, да, я могу, но этот переключатель принимает только одну переменную, я хочу обработать три. - person BanditoBunny; 01.11.2011
comment
@BanditoBunny - Тогда ответ - нет, вы не можете этого сделать. - person JonH; 01.11.2011
comment
-1: правильный ответ, кроме вашего переключателя. Удалите это, и я удалю отрицательный голос. - person John Saunders; 01.11.2011
comment
Неправильно != Возможно - То, что вы хотите, невозможно, поэтому мое решение не является неправильным - оно просто не соответствует вашему комбинаторному взрыву :) (мягко говоря). - person JonH; 01.11.2011
comment
@JonH - Это даже близко не соответствует тому, что хочет автор. Конечно, то, что он хочет, не может быть сделано в соответствии со спецификацией языка и не должно этого делать. Это делает что-то совершенно другое, ужасно выглядящий код Рэймонда Чена был близок к тому, что хотел автор. - person Security Hound; 01.11.2011
comment
@ Ramhound - это именно моя точка зрения ... Я знаю, что это не делает того, чего хочет ОП, потому что то, что хочет ОП, невозможно. - person JonH; 01.11.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

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

person Jim Mischel    schedule 01.11.2011