我试图用system.linq.expressions创建一个开关表达式:

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(…)中的错误吗?
在C中,只有“默认”部分的开关是正确的:
switch(expr) {
default:
  return 0;
}//switch

upd:我已将an issue提交给github上的corefx回购协议

最佳答案

在c_sswitchSwitchExpression之间没有完全的类比。在另一个方向,考虑一下你可以拥有:

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是这样设置的,除非它简化了一个表达式没有实例和没有默认体的情况。这就是说,我认为这可能只是一个表达的问题,通常打算有多个案例,这就是它被编码支持。
我的submitted a pull-request to .NET Core允许这样的无大小写表达式,生成一个SwitchExpression,其中switchValue类型的默认值与默认体具有相同的体。这种方法意味着任何对SwitchExpression感到意外的情况都应该仍然处理,避免向后兼容问题。没有默认值的情况也可以通过创建一个不执行任何操作的noop表达式来处理,因此现在仍然抛出ArgumentException的唯一情况是,如果没有case和no default,并且类型显式设置为void以外的值,这种情况在显然必须保留的输入规则下是无效的。
[更新:该方法被拒绝,但a later pull-request被接受,因此,.NETCARE允许的情况下SwitchExpression s,尽管如果和其他版本采用.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);
}

等等,然后可以添加。
这导致一个trimmerExpression总的来说比我的pull请求,因为它在没有案例的情况下完全切断了switch,只返回默认的主体。如果您真的需要一个SwitchExpression,那么您可以创建一个类似的helper方法,该方法遵循与pull请求在创建一个新的SwitchCase并使用它时所采用的逻辑相同的逻辑。

08-03 13:12