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

问题描述

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,CultureInfo culture)
{
  if (values != null && values.Length == 3 && 
     values[0] is [float,double,decimal] v1 &&
     values[1] is [float,decimal] v2 &&
     values[2] is [float,decimal] v3 &&
  )
    return v1 * v2 * v3;
  else
    throw new ArgumentException("All parameters must be of numeric type.");
}

显然没有什么用,只是为了分享我的想法。 :)

解决方法

不,不是。

您最好的选择是编写两个模式匹配的if声明(或if / else if组合,具体取决于您的首选逻辑流程),并使用两个结果类型为{ {1}}和float分别作为同一功能的输入,因此double块内的代码不会重复。

,

编辑:

在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:

select *

我会毫不犹豫地建议强制转换为浮点数,因为如果发送双精度数可能不合适,等等。(并且如注释中所指出的那样,如果将小数作为对象装箱,则不能将其拆箱,并且使其同时漂浮)

您可能最终会对所有内容进行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

:)

,

不,它不会;最初的问题从根本上来说是有缺陷的,因为C#不支持union types,所以这样的重复变量名 不能用来指代“两种”值。这意味着没有变量v可以强类型为float|double 1 。将变量键入为对象是原始问题。

此外,C#不允许有意义地使用if (x is A a || x is B b) 。这是因为未分配 a 或未分配 b,从而有条件地使用了毒药案中的任何一个变量,并且编译失败。相反,使用if (x is A a && x is B b)是可行的保护措施,因为两者 ab都可以得到分配。

关于方法/算法本身,以相同的方式编写这两种方法时,需要相同数量的保护和强制转换来实现解决方案;仅不同变量的数量发生变化。但是,这在使用强制转换的原始实现中被一个错误掩盖了。

错误显示的“旧样式”强制转换代码具有(float)x,其中x键入为object。对于双精度和十进制值,这将在运行时失败。正确强制转换的唯一方法是使用正确的强制转换,每个强制转换都有正确完善的防护每次强制转换,无论是捕获到变量还是其他变量: 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 而不是保护每个值类型。通过更改算法,首先将每个值转换为浮点数,它还减少了类型之间求和的组合总数。一个等效的本地方法可以用3个警卫来编写。

1 泛型在这里无济于事,因为运算符不是多态的,并且没有有效的方法来约束T来解决此问题(例如,没有ISummable)。

2 此答案不对等效形式做出判断。唯一的区别是命名变量的数目和正在转换的表达式的求值数目。如果为每种类型使用直接强制转换正确地编写了原始代码,以免导致运行时失败,则强制转换的次数(以及组合的数目)与使用防护的次数相同。


3 这里是说明问题的一种方法,同时说明了“完全隐藏”和“浮起来”效果。如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;

这还将处理固定的按序列设置:

foreach (var o in new[] { v[2],v[4],v[6] }) ..