С# 7.0: включить System.Type

Никакой существующий вопрос не имеет ответа на этот вопрос.

В С# 7 можно напрямую переключиться на System.Type?

Когда я пытаюсь:

    switch (Type)
    {
      case typeof(int):
        break;
    }

он говорит мне, что typeof(int) должно быть постоянным выражением.

Есть ли какой-то синтаксический сахар, который позволяет мне избегать case nameof(int): и напрямую сравнивать типы для равенства? nameof(T) в аргументе case не совсем хорошо, потому что пространства имен. Поэтому, хотя столкновение имен, вероятно, не применимо для int, оно применимо для других сравнений.

Другими словами, я пытаюсь быть более прямым, чем это:

    switch (Type.Name)
    {
      case nameof(Int32):
      case nameof(Decimal):
        this.value = Math.Max(Math.Min(0, Maximum), Minimum); // enforce minimum 
        break;
    }

Ответ 1

Эта возможность привязки (уже связанная) позволяет сопоставить

Обычно вы включаете значение:

switch (this.value) {
  case int intValue:
    this.value = Math.Max(Math.Min(intValue, Maximum), Minimum);
    break;
  case decimal decimalValue:
    this.value = Math.Max(Math.Min(decimalValue, Maximum), Minimum);
    break;
}

Но вы можете использовать его для включения типа, если все, что у вас есть, это тип:

switch (type) {
  case Type intType when intType == typeof(int):
  case Type decimalType when decimalType == typeof(decimal):
    this.value = Math.Max(Math.Min(this.value, Maximum), Minimum);
    break;
}

Обратите внимание, что это не то, для чего предназначена эта функция, она становится менее читаемой, чем традиционная цепочка if... else if... else if... else, а традиционная цепочка что он компилирует в любом случае. Я не рекомендую использовать сопоставление шаблонов, как это.

Ответ 2

Начиная с идеи Paulustrious о включении константы, но стремящейся к большей читаемости:

  Type type = GetMyType();
  switch (true)
  {
    case bool _ when type == typeof(int):
      break;
    case bool _ when type == typeof(double):
      break;
    case bool _ when type == typeof(string):
      break;
    default:
      break;
  }

Что читаемо субъективно. Раньше я делал что-то подобное в VB, поэтому я привык к этой форме (но в VB bool _ не нужен, чтобы его там не было). К сожалению, в С# требуется bool _. Я использую С# 7.0, и я думаю, что включение константы может не поддерживаться в более ранних компиляторах, но я не уверен в этом, поэтому попробуйте, если захотите. Мне кажется забавным, что форматировщик кода S/O еще не знает о when.

Вы не хотели бы этого делать, конечно, если вам нужна переменная case, как и для подклассов.

Но для произвольных булевых выражений он более подходит, например:

  switch (true)
  {
    case bool _ when extruder.Temperature < 200:
      HeatUpExtruder();
      break;
    case bool _ when bed.Temperature < 60:
      HeatUpBed();
      break;
    case bool _ when bed.Y < 0 || bed.Y > 300:
      HomeYAxis();
      break;
    default:
      StartPrintJob();
      break;
  }

Некоторые утверждают, что это хуже, чем если бы.... Единственное, что я могу сказать: switch заставляет один путь, и невозможно разбить оператор switch, но можно оставить else и разбить if if..else на несколько операторов непреднамеренно, возможно, выполнить две "ветки" случайно.

Включение Type - это действительно просто произвольный коммутатор, потому что то, что мы действительно включаем, является свойством переменной. Если и до тех пор, пока мы не сможем сделать case typeof(int) (case на то, что не является постоянным выражением), мы застряли с чем-то вроде этого, если мы не хотим использовать строковые константы, которые в случае имен типов, не входят в рамки.

Ответ 3

Проблема, поднимаемая здесь OP, заключается в том, что вы не можете использовать новую функцию С# 7 на основе типа, когда у вас нет фактического экземпляра включенного типа. доступны, и вместо этого у вас есть только его предполагаемый System.Type. принятый ответ, резюмированный следующим образом, хорошо работает для точного соответствия типов (здесь показано небольшое улучшение, но см. мой последний пример ниже для дальнейшей оптимизации)...

Type type = ...
switch (type)
{
    case Type _ when type == typeof(Int32):
    case Type _ when type == typeof(Decimal):
        this.value = Math.Max(Math.Min(this.value, Maximum), Minimum);
        break;
}

... но важно отметить, что для производных иерархий ссылочных типов это не будет демонстрировать то же поведение, что и цепочка if... else, в которой для сопоставления используется ключевое слово is. Рассмотрим:

class TBase { }
class TDerived1 : TBase { }
class TDerived2 : TBase { }
class TDerived3 : TDerived2 { }

TBase inst = ...

if (inst is TDerived1)
{
    // Handles case TDerived1
}
else if (inst is TDerived2)
{
    // Handles cases TDerived2 and TDerived3
}
else if (inst is TDerived3)
{
    // NOT EXECUTED                            <---  !
}

Поскольку TDerived3 "is-a" TDerived2, оба случая обрабатываются более ранним условием при использовании сопоставления is. Это подчеркивает различную семантику времени выполнения между "строгим" или "точным" типом равенства по сравнению с более тонким понятием типа совместимости. Поскольку типы в вопросе OP были примитивами ValueType (которые не могут быть получены из), разница не могла иметь значения. Но если мы адаптируем "точное совпадение типов" принятого ответа с примерами классов, показанными выше, мы получим другой результат:

Type type = ...

switch (type)
{
    case Type _ when type == typeof(TDerived1):
        // Handles case TDerived1
        break;

    case Type _ when type == typeof(TDerived2):
        // Handles case TDerived2
        break;

    case Type _ when type == typeof(TDerived3):
        // Handles case TDerived3              <---  !
        break;
}

Фактически, С# 7 даже не скомпилирует инструкцию switch, которая соответствует последовательности if / else, показанной ранее. (nb Похоже, что компилятор должен определить это как предупреждение, а не как ошибку, поскольку безвредный результат - это просто ветвь недоступного кода - условие, которое компилятор считает предупреждением в другом месте), а также учитывая, что компилятор делает это даже не обнаружил, казалось бы, идентичную ситуацию в версии if / else). Вот что:

enter image description here

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

Type type = ...

switch (type)
{
    case Type _ when typeof(TDerived1).IsAssignableFrom(type):
        // Handles case TDerived1
        break;

    case Type _ when typeof(TDerived2).IsAssignableFrom(type):
        // Handles cases TDerived2 and TDerived3
        break;

    case Type _ when typeof(TDerived3).IsAssignableFrom(type):
        // NOT EXECUTED                       <-- !
        break;
}

Наконец, как я уже упоминал в другом ответе на этой странице, вы можете еще больше упростить использование оператора switch. Поскольку мы используем только функциональность предложения when, и, поскольку у нас, по-видимому, все еще есть исходный включенный экземпляр, доступный в переменной, нет необходимости упоминать эту переменную в операторе switch или повторять ее тип (Type, в данном случае) в каждом case. Просто сделайте следующее:

Type type = ...

switch (true)
{
    case true when typeof(TDerived1).IsAssignableFrom(type):
        break;

    case true when typeof(TDerived2).IsAssignableFrom(type):
        break;

    case true when typeof(TDerived3).IsAssignableFrom(type):
        break;
}

Обратите внимание на switch(true) и case(true). Я рекомендую эту более простую технику всякий раз, когда вы полагаетесь только на предложение when (то есть, помимо ситуации включения System.Type, как описано здесь).

Ответ 4

@toddmo предложил следующее:

switch (true)
{
    case bool _ when extruder.Temperature < 200:
        HeatUpExtruder();
        break;

    // etc..
    default:
        StartPrintJob();
        break;
}

... но почему бы не пойти еще дальше в его стремлении к простоте. Следующее работает так же хорошо, не нуждаясь ни в квалификации типа bool, ни в посторонней переменной _ dummy:

switch (true)
{
    case true when extruder.Temperature < 200:
        HeatUpExtruder();
        break;

    // etc.
    default:
        StartPrintJob();
        break;
}

Ответ 5

Я нашел простой и эффективный способ. Для этого требуется С# V7. switch("") означает, что все случаи будут выполнены до предложения when. Он использует предложение when, чтобы посмотреть на type.

List<Object> parameters = new List<object>(); // needed for new Action
parameters = new List<object>
{
    new Action(()=>parameters.Count.ToString()),
    (double) 3.14159,
    (int) 42,
    "M-String theory",
    new System.Text.StringBuilder("This is a stringBuilder"),
    null,
};
string parmStrings = string.Empty;
int index = -1;
foreach (object param in parameters)
{
    index++;
    Type type = param?.GetType() ?? typeof(ArgumentNullException);
    switch ("")
    {
        case string anyName when type == typeof(Action):
            parmStrings = $"{parmStrings} {(param as Action).ToString()} ";
            break;
        case string egStringBuilder when type == typeof(System.Text.StringBuilder):
            parmStrings = $"{parmStrings} {(param as System.Text.StringBuilder)},";
            break;
        case string egInt when type == typeof(int):
            parmStrings = $"{parmStrings} {param.ToString()},";
            break;
        case string egDouble when type == typeof(double):
            parmStrings = $"{parmStrings} {param.ToString()},";
            break;
        case string egString when type == typeof(string):
            parmStrings = $"{parmStrings} {param},";
            break;
        case string egNull when type == typeof(ArgumentNullException):
            parmStrings  = $"{parmStrings} parameter[{index}] is null";
            break;
        default: throw new System.InvalidOperationException();
    };
} 

Это оставляет элементы parmStrings, содержащие...

System.Action 3.14159, 42, M-String theory, This is a stringBuilder, parameter[5] is null

Ответ 6

Добавляя к вышеуказанному ответу, если вам не нужно значение, которое будет использоваться внутри оператора case, вы можете использовать _. Этот синтаксис можно использовать для удаления предупреждающего сообщения неиспользуемой переменной.

switch (this.value) {
  case int _:
    //Do something else without value.
    break;
  case decimal decimalValue:
    this.value = Math.Max(Math.Min(decimalValue, Maximum), Minimum);
    break;
}

Ответ 7

Вот альтернатива, которая не будет компилироваться по мере отсутствия классов:

bool? runOnUI = queuedAction.RunOnUI;  // N=null, T=true F=False
bool isOnUI = Statics.CoreDispatcher.HasThreadAccess;
bool isFF = queuedAction.IsFireAndForget;   // just makes it easier to read the switch statement
if (false == queuedAction.IsFireAndForget)
    IsOtherTaskRunning = true;

/* In the case statements below the string name is something like noFF_TN
 * The compiler ignores the string name. I have used them here to represent
 * the logic in the case statement:   ( FF = FireAnd Forget, T=true, F=false, N = null, * means 'whatever')
 * 
 *      isFF_** = FireAndForget QueuedAction
 *      noFF_** = Not FireAndForget so Wait for Task to Finish (as opposed to Complete)
 *      ****_T* = We are already on the UI thread 
 *      ****_F* = We are not on the UI thread 
 *      ****_*T = Run on the UI thread.
 *      ****_*F = Do not run on UI thread
 *      ****_*N = Don't care so run on current thread
 *      
 *  The last character is a "bool?" representing RunOnUI. It has
 *  three values each of which has a different meaning */

bool isTask;
switch ("ignore")
{
    /* Run it as an Action (not Task) on current Thread   */
    case string noFF_TT when !isFF && isOnUI && runOnUI == true:
    case string isFF_TN when isFF && isOnUI && !runOnUI == null:
    case string isFF_FN when isFF && !isOnUI && runOnUI == null:
    case string isFF_TT when isFF && isOnUI && runOnUI == true:
    case string isFF_FF when isFF && !isOnUI && runOnUI == false:
        isTask = false;
        queuedAction.ActionQA(queuedAction); // run as an action, not as a Task
        break;

    /* Create a Task, Start it */

    case string isFF_TF when isFF && isOnUI == true && runOnUI == false:
    case string noFF_TN when !isFF && isOnUI == true && runOnUI == null:     // <== not sure if I should run it on UI instead
    case string noFF_TF when !isFF && isOnUI && runOnUI == false:
    case string noFF_FN when !isFF && !isOnUI && runOnUI == null:
    case string noFF_FF when !isFF && !isOnUI && runOnUI == false:
        var cancellationTokenSource = new CancellationTokenSource();
queuedAction.Canceller?.SetCancellationTokenSource(cancellationTokenSource);
        isTask = true;
        new Task
            (
                (action) => queuedAction.ActionQA(queuedAction),
                queuedAction,
                cancellationTokenSource.Token
            )
            .Start();
        break;

    /* Run on UI and don't wait */

    case string isFF_FT when isFF && !isOnUI && runOnUI == true:
        isTask = true;
        Statics.RunOnUI(() => queuedAction.ActionQA(queuedAction), asTaskAlways: true);
        break;

    /* Run on the UI as a Task and Wait */

    case string noFF_FT when !isFF && !isOnUI && runOnUI == true:
        isTask = true;
        Statics.RunOnUI(() => queuedAction.ActionQA(queuedAction), asTaskAlways: true);
        break;

    default:
        throw new LogicException("unknown case");
}