在System.Linq.Expressions中,没有case的switch语句(但有默认情况)

12

我尝试使用System.Linq.Expressions创建一个switch表达式:

var value = Expression.Parameter(typeof(int));
var defaultBody = Expression.Constant(0);
var cases1 = new[] { Expression.SwitchCase(Expression.Constant(1), Expression.Constant(1)), };
var cases2 = new SwitchCase[0];
var switch1 = Expression.Switch(value, defaultBody, cases1);
var switch2 = Expression.Switch(value, defaultBody, cases2);

但在最后一行,我遇到了一个 ArgumentException:

需要非空集合。参数名: cases

这个异常的原因是什么?也许这是 Expression.Switch(…) 中的一个 bug 吗?

在 C# 中,只有带有“default”部分的 switch 是正确的:

switch(expr) {
default:
  return 0;
}//switch

更新:我已经在 GitHub 的 CoreFX 存储库中提交了问题


2
这种结构的目标是什么?带有default但没有caseswitch语句将只执行default - Dmitry Bychenko
对我来说,没有case的switch看起来毫无意义,所以我认为这个异常是合理的。 - Andrey Korneyev
是的,C#规范指出switch块有零个或多个switch部分;但这并不意味着switch表达式必须符合C#规范。由于您在运行时创建表达式,因此可以通过一种解决方法,简单地添加一个值为!= switch值的Expression.SwitchCase;或者将默认情况的主体作为具有值= switch值的switch case添加。 - sloth
4
虽然我想不出为什么需要C#的等价物,但我确实能看出在动态创建开关时这会很有用;如果您将具有可变数量的SwitchCase取决于某些条件,则无需特殊处理其为零的情况。 - Jon Hanna
@JonHanna 是的,这正是我的情况。 - Viacheslav Ivanov
1个回答

7

C#的switchSwitchExpression之间没有完全的类比。从另一个方向考虑,您可以有:

var value = Expression.Parameter(typeof(int));
var meth = Expression.Lambda<Func<int, string>>(
  Expression.Switch(
    value,
    Expression.Call(value, typeof(object).GetMethod("ToString")),
    Expression.SwitchCase(Expression.Constant("Zero"), Expression.Constant(0, typeof(int))),
    Expression.SwitchCase(Expression.Constant("One"), Expression.Constant(1, typeof(int)))),
    value
  ).Compile();
Console.WriteLine(meth(0)); // Zero
Console.WriteLine(meth(1)); // One
Console.WriteLine(meth(2)); // 2

这里的SwitchExpression返回一个值,而switch不能这样做。

因此,仅仅能够使用SwitchExpression并不意味着你可以使用switch来实现同样的功能,同样地,仅仅能够使用switch也并不意味着你可以使用SwitchExpression

话虽如此,我认为没有很好的理由将SwitchExpression设置成这样,除非是为了简化表达式没有case和默认体的情况。但是,我认为这可能只是表达式通常被设计用于具有多个case,并且被编码以支持这一点。

提交了一个.NET Core的pull-request, 该请求将允许这种无大小写表达式,通过生成一个SwitchExpression,其中switchValue类型的默认值具有与默认体相同的内容来实现。这种方法意味着任何对于没有case的SwitchExpression会感到惊讶的事情仍然可以处理,避免了向后兼容性问题。如果没有默认值,则创建一个不执行任何操作的noop表达式来处理,因此现在仅在没有case没有default类型明确设置为void以外的其他值的情况下仍会抛出ArgumentException,这种情况在必须显然保留的类型规则下无效。
[更新:该方法被拒绝,但稍后的pull-request已被接受,因此.NET Core现在允许无case的SwitchExpression, 但是是否被其他版本的.NET采用是另一回事]。

同时,如果您使用的是其他版本的.NET,则最好使用像这样的辅助方法:

public static Expression SwitchOrDefault(Type type, Expression switchValue, Expression defaultBody, MethodInfo comparison, IEnumerable<SwitchCase> cases)
{
  if (cases != null)
  {
    // It's possible that cases is a type that can only be enumerated once.
    // so we check for the most obvious condition where that isn't true
    // and otherwise create a ReadOnlyCollection. ReadOnlyCollection is
    // chosen because it's the most efficient within Switch itself.
    if (!(cases is ICollection<SwitchCase>))
      cases = new ReadOnlyCollection<SwitchCase>(cases);
    if (cases.Any())
      return Switch(type, switchValue, defaultBody, comparison, cases);
  }
  return Expression.Block(
    switchValue, // include in case of side-effects.
    defaultBody != null ? defaultBody : Expression.Empty() // replace null with a noop expression.
  );
}

如下所示的重载:

public static Expression SwitchOrDefault(Expression switchValue, Expression defaultBody, params SwitchCase[] cases)
{
  return SwitchOrDefault(switchValue, defaultBody, null, (IEnumerable<SwitchCase>)cases);
}

然后可以添加等等。

这将导致比我的拉取请求更简洁的Expression,因为它完全削减了在无情况的情况下的switch并只返回默认体。如果您确实需要一个SwitchExpression,那么您可以创建一个类似的帮助方法,遵循与该拉取请求相同的逻辑来创建新的SwitchCase,然后使用它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接