Переключение без случаев (но со значением по умолчанию) в System.Linq.Expressions

Я попытался создать выражение switch с помощью 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:

Требуется непустая коллекция. Имя параметра: случаи

В чем причина этого исключения? Возможно, это ошибка в Expression.Switch(...)?

В С# переключатель только с "по умолчанию" является правильным:

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

UPD: я отправил проблему в репозиторий CoreFX на GitHub

Ответ 1

Не существует полной аналогии между С# switch и SwitchExpression. В другом направлении подумайте, что вы можете:

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 был установлен таким образом, за исключением того, что, возможно, это упрощает случай, когда выражение не имеет случаев и без тела по умолчанию. Тем не менее, я думаю, что это, скорее всего, просто вопрос выражения, которое обычно предназначено для нескольких случаев, и именно это было закодировано для поддержки.

Я отправил pull-request в .NET Core, который позволил бы использовать такие безрезультатные выражения, создав SwitchExpression где значение по умолчанию для типа switchValue имеет ту же структуру, что и тело по умолчанию. Этот подход означает все, что было бы удивлено SwitchExpression, при этом ни один случай не должен справляться, избегая проблем с обратной совместимостью. Случай с отсутствием дефолта либо обрабатывается созданием noop-выражения, которое ничего не делает, поэтому единственный случай, который теперь все еще бросает ArgumentException, - это если нет аргумента и по умолчанию и, тип явно задан чем-то другим, кроме void, этот случай является недопустимым в соответствии с правилами ввода, которые, очевидно, должны сохраняться.

[Обновление: этот подход был отклонен, но был принят более поздний запрос на тягу, поэтому без-case SwitchExpression теперь разрешено .NET Core, хотя если и когда это принято другими версиями .NET, это другое дело].

В то же время, или если вы используете другую версию .NET, вы можете использовать хелпер-метод, например:

public static Expression SwitchOrDefault(Type type, Expression switchValue, Expression defaultBody, MethodInfo comparison, IEnumerable<SwitchCase> cases)
{
  if (cases != null)
  {
    // It 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 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 в целом, чем мой запрос pull, потому что он полностью исключает switch в случайном случае и просто возвращает тело по умолчанию. Если у вас действительно должен быть SwitchExpression, тогда вы можете создать похожий вспомогательный метод, который будет следовать той же логике, что и запрос pull-запроса при создании нового SwitchCase, а затем используя это.