Оператор переключения нескольких переменных в С#

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

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

   etc ....
}

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

Ответы

Ответ 1

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

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

В основном это не что иное, как предоставление удобного синтаксиса для проверки нескольких значений - и использование нескольких ifs вместо коммутатора.

Ответ 2

Вы можете сделать это в С# 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;
}

Ответ 3

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

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

Ответ 4

Мои безумные берут на себя это:

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;
    }
}

Ответ 5

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

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

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

Ответ 7

В соответствии с спецификацией языка С# выражение оператора switch должно быть разрешено к одному из sbyte, byte, sbyte, byte, short, ushort, int, uint, long, ulong, char, string или enum-type. Это означает, что вы не можете включить 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;
}

Ответ 8

Если вы хотите переключить несколько входов и вернуть значение, в С# 8 вы можете использовать шаблоны кортежей. Это форма выражения переключателя сопоставления с образцом, которая принимает несколько значений в качестве входных данных в форме кортежа. Вот пример использования ваших входных данных; обратите внимание на использование _ для случая по умолчанию:

public static string SwitchDemo(int intVal1, string strVal2, bool boolVal3) =>
    (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"
    };

Ответ 9

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

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

В следующем примере показано, что пропустить с одного ярлыка случая другому можно для пустых ярлыков case:

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

Ответ 10

if (a == 1 && b == 1) {}
else if (a == 1 && b == 2) {}
else if (a == 2 && b ==2) {}

Ответ 11

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

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