C# 模式匹配是否支持多种类型?
Does C# pattern matching support multiple types?
C#模式匹配是否支持多种类型?例如我们可以做这样的事情:
if(value is float v || value is double v)
这当然不行;不能使用变量名两次。此外,我们不能使用两个不同的名称,如 v1
和 v2
,因为那样我们将如何引用正确匹配的模式变量,这将有效地扼杀模式匹配的目的。
编辑
为了让事情收敛,这是我最终得到的(没有模式匹配):
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values != null && values.Length == 3 && (values[0] is float || values[0] is double || values[0] is decimal) && (values[1] is float || values[1] is double || values[1] is decimal) && (values[2] is float || values[2] is double || values[2] is decimal))
return (float)values[0] * (float)values[1] * (float)values[2];
else
throw new ArgumentException("All parameters must be of numeric type.");
}
其中涉及一些向下转换,但就我的目的而言,它们不会导致大量信息丢失。
在我的理想世界里,我希望这样写:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values != null && values.Length == 3 &&
values[0] is [float, double, decimal] v1 &&
values[1] is [float, double, decimal] v2 &&
values[2] is [float, double, decimal] v3 &&
)
return v1 * v2 * v3;
else
throw new ArgumentException("All parameters must be of numeric type.");
}
这显然行不通,但只是为了分享我的想法。 :)
不,不是。
最好的办法是编写两个模式匹配 if
语句(或 if
/else if
组合,具体取决于您首选的逻辑流程)并使用两个结果变量分别键入 float
和 double
作为同一函数的输入,因此 if
块中的代码不会重复。
编辑:
答案是在 OP 澄清他们希望匹配的变量具有 相同的名称 之前发布的。保留原来的答案:
您可以在 switch
中做类似的事情 - 它可能更有用,因为您可能需要不同的代码,具体取决于类型。
object test = 25.5f;
switch (test)
{
case float f:
Console.WriteLine("I'm a float: " + f);
break;
case double d:
Console.WriteLine("I'm a double: " + d);
break;
}
如何使用 LINQ 进行整理:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if ((values?.Length ?? 0) == 3 && values.All(v => v is double || v is decimal || v is float)
...
else
throw new ArgumentException("All parameters must be of numeric type.");
}
我会犹豫是否推荐转换为浮动,因为如果发送双精度数,它们可能不适合,等等。它并同时将其投射到浮动)
你可能最终会对每件事都做 Convert.ToDecimal
,或者像这样:
decimal d = 1;
foreach(object o in values)
if(o is float v)
d *= v;
else if(o is double vv)
d *= vv;
else if(o is decimal vvv)
d *= vvv;
else
throw ...
:)
不,它没有;并且最初的 ask 存在根本性的缺陷,因为这样一个重复的变量名 cannot 可以引用“任一”类型的值,因为 C# not 支持union types。这意味着 没有变量 v
可以强类型化为 float|double
1。将变量类型化为对象,是原始问题。
此外,C# 不允许对 if (x is A a || x is B b)
进行有意义的使用。这是因为 either a
是未赋值的 or b
是未赋值的,使得在条件 a 中访问任一变量poison-pill 案例 — 编译失败。相反,使用 if (x is A a && x is B b)
是一个可行的守卫,因为 a
和 b
都可以保证被分配。
至于 approach/algorithm 本身,需要完全相同数量的守卫和转换来实现解决方案 当以相同的方式编写两者时;只有不同变量的数量会发生变化。但是,这被带有转换的原始实现中的错误隐藏了。
错误呈现的“旧式”转换代码具有 (float)x
,其中 x 被键入为 object
。对于双精度值和十进制值,这将在 run-time 处失败。正确投射的唯一方法是使用正确的投射,每个投射都有正确改进的守卫 per cast,无论是捕获到变量还是其他方式:2
object x = (double)1;
// InvalidCastException - can’t cast System.Single to System.Double
float f = (float)x;
// ok! cast to double, convert to float
float f2 = (float)(double)x;
将原始代码切换为使用 Convert.ToSingle
将解决此问题 同时继续隐藏 函数内的有效强制转换次数并且 不同的算法 3 比保护每个 value-type。通过改变算法,首先将每个值转换为浮点数,也减少了类型之间求和的组合总数。可以用 3 个守卫编写等效的本地方法。
1泛型在这里没有帮助,因为运算符不是多态的,并且没有有效的方法来约束 T 这个问题(例如,没有 ISummable)。
2这个答案不对等价形式做出判断。唯一的区别是命名变量的数量和正在转换的表达式的计算次数。当使用每种类型的直接转换正确编写源代码时,为了不导致运行时失败,转换的数量——以及组合的数量——与使用守卫时相同。
3这是在说明 hiding-in-ToSingle 和 collapsing-to-float 效果时构建问题的一种方法。我可能会 直接 将守卫拉入序列循环,如 Jard 的回答所示。
float? ToFloat(object x) {
if (x is float f) return f;
if (x is double d) return (float)d;
if (x is decimal m) return (float)m;
return null;
}
然后使用固定数量的变量:
if (v != null && v.Length >= 3) {
var v0 = ToFloat(v[0]);
var v1 = ToFloat(v[1]);
var v2 = ToFloat(v[2]);
if (v0.HasValue && v1.HasValue && v2.HasValue)
return v0.Value + v1.Value + v2.Value;
}
throw ..
或跨对象序列的用法:
float r = 0;
foreach (var o in seq) {
var v = ToFloat(o);
if (!v.HasValue)
throw ..
r += v.Value;
}
return r;
这也适用于固定 set-as-a-sequence:
foreach (var o in new[] { v[2], v[4], v[6] }) ..
C#模式匹配是否支持多种类型?例如我们可以做这样的事情:
if(value is float v || value is double v)
这当然不行;不能使用变量名两次。此外,我们不能使用两个不同的名称,如 v1
和 v2
,因为那样我们将如何引用正确匹配的模式变量,这将有效地扼杀模式匹配的目的。
编辑
为了让事情收敛,这是我最终得到的(没有模式匹配):
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values != null && values.Length == 3 && (values[0] is float || values[0] is double || values[0] is decimal) && (values[1] is float || values[1] is double || values[1] is decimal) && (values[2] is float || values[2] is double || values[2] is decimal))
return (float)values[0] * (float)values[1] * (float)values[2];
else
throw new ArgumentException("All parameters must be of numeric type.");
}
其中涉及一些向下转换,但就我的目的而言,它们不会导致大量信息丢失。
在我的理想世界里,我希望这样写:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values != null && values.Length == 3 &&
values[0] is [float, double, decimal] v1 &&
values[1] is [float, double, decimal] v2 &&
values[2] is [float, double, decimal] v3 &&
)
return v1 * v2 * v3;
else
throw new ArgumentException("All parameters must be of numeric type.");
}
这显然行不通,但只是为了分享我的想法。 :)
不,不是。
最好的办法是编写两个模式匹配 if
语句(或 if
/else if
组合,具体取决于您首选的逻辑流程)并使用两个结果变量分别键入 float
和 double
作为同一函数的输入,因此 if
块中的代码不会重复。
编辑:
答案是在 OP 澄清他们希望匹配的变量具有 相同的名称 之前发布的。保留原来的答案:
您可以在 switch
中做类似的事情 - 它可能更有用,因为您可能需要不同的代码,具体取决于类型。
object test = 25.5f;
switch (test)
{
case float f:
Console.WriteLine("I'm a float: " + f);
break;
case double d:
Console.WriteLine("I'm a double: " + d);
break;
}
如何使用 LINQ 进行整理:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if ((values?.Length ?? 0) == 3 && values.All(v => v is double || v is decimal || v is float)
...
else
throw new ArgumentException("All parameters must be of numeric type.");
}
我会犹豫是否推荐转换为浮动,因为如果发送双精度数,它们可能不适合,等等。它并同时将其投射到浮动)
你可能最终会对每件事都做 Convert.ToDecimal
,或者像这样:
decimal d = 1;
foreach(object o in values)
if(o is float v)
d *= v;
else if(o is double vv)
d *= vv;
else if(o is decimal vvv)
d *= vvv;
else
throw ...
:)
不,它没有;并且最初的 ask 存在根本性的缺陷,因为这样一个重复的变量名 cannot 可以引用“任一”类型的值,因为 C# not 支持union types。这意味着 没有变量 v
可以强类型化为 float|double
1。将变量类型化为对象,是原始问题。
此外,C# 不允许对 if (x is A a || x is B b)
进行有意义的使用。这是因为 either a
是未赋值的 or b
是未赋值的,使得在条件 a 中访问任一变量poison-pill 案例 — 编译失败。相反,使用 if (x is A a && x is B b)
是一个可行的守卫,因为 a
和 b
都可以保证被分配。
至于 approach/algorithm 本身,需要完全相同数量的守卫和转换来实现解决方案 当以相同的方式编写两者时;只有不同变量的数量会发生变化。但是,这被带有转换的原始实现中的错误隐藏了。
错误呈现的“旧式”转换代码具有 (float)x
,其中 x 被键入为 object
。对于双精度值和十进制值,这将在 run-time 处失败。正确投射的唯一方法是使用正确的投射,每个投射都有正确改进的守卫 per cast,无论是捕获到变量还是其他方式:2
object x = (double)1;
// InvalidCastException - can’t cast System.Single to System.Double
float f = (float)x;
// ok! cast to double, convert to float
float f2 = (float)(double)x;
将原始代码切换为使用 Convert.ToSingle
将解决此问题 同时继续隐藏 函数内的有效强制转换次数并且 不同的算法 3 比保护每个 value-type。通过改变算法,首先将每个值转换为浮点数,也减少了类型之间求和的组合总数。可以用 3 个守卫编写等效的本地方法。
1泛型在这里没有帮助,因为运算符不是多态的,并且没有有效的方法来约束 T 这个问题(例如,没有 ISummable)。
2这个答案不对等价形式做出判断。唯一的区别是命名变量的数量和正在转换的表达式的计算次数。当使用每种类型的直接转换正确编写源代码时,为了不导致运行时失败,转换的数量——以及组合的数量——与使用守卫时相同。
3这是在说明 hiding-in-ToSingle 和 collapsing-to-float 效果时构建问题的一种方法。我可能会 直接 将守卫拉入序列循环,如 Jard 的回答所示。
float? ToFloat(object x) {
if (x is float f) return f;
if (x is double d) return (float)d;
if (x is decimal m) return (float)m;
return null;
}
然后使用固定数量的变量:
if (v != null && v.Length >= 3) {
var v0 = ToFloat(v[0]);
var v1 = ToFloat(v[1]);
var v2 = ToFloat(v[2]);
if (v0.HasValue && v1.HasValue && v2.HasValue)
return v0.Value + v1.Value + v2.Value;
}
throw ..
或跨对象序列的用法:
float r = 0;
foreach (var o in seq) {
var v = ToFloat(o);
if (!v.HasValue)
throw ..
r += v.Value;
}
return r;
这也适用于固定 set-as-a-sequence:
foreach (var o in new[] { v[2], v[4], v[6] }) ..