С# 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");
}