C# 模式匹配是否支持多种类型?

Does C# pattern matching support multiple types?

C#模式匹配是否支持多种类型?例如我们可以做这样的事情:

if(value is float v || value is double v)

这当然不行;不能使用变量名两次。此外,我们不能使用两个不同的名称,如 v1v2,因为那样我们将如何引用正确匹配的模式变量,这将有效地扼杀模式匹配的目的。

编辑

为了让事情收敛,这是我最终得到的(没有模式匹配):

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 组合,具体取决于您首选的逻辑流程)并使用两个结果变量分别键入 floatdouble 作为同一函数的输入,因此 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|double1。将变量类型化为对象,是原始问题。

此外,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) 是一个可行的守卫,因为 ab 都可以保证被分配。

至于 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] }) ..