I have tried to create a switch expression with 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);
but in the last line I get an ArgumentException:
Non-empty collection required. Parameter name: cases
What is the reason of this exception? May be this a bug in Expression.Switch(…)?
In a C# a switch with "default" part only is correct:
switch(expr) {
default:
return 0;
}//switch
UPD: I have submitted an issue to the CoreFX repo on GitHub
There isn't a complete analogy between C#'s switch
and SwitchExpression
. In the other direction, consider that you can have:
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
Here the SwitchExpression
returns a value which is something switch
cannot do.
So, just as being able to do something with SwitchExpression
does not mean you can do it with a switch
, so too there's no reason to assume that being able to do something with a switch
means you can do it with a SwitchExpression
.
That said, I see no good reason why SwitchExpression
was set this way, except perhaps that it simplifies the case where an expression has no cases and no default body. That said, I think this was likely just a matter of the expression being generally intended to have multiple cases, and that was what it was coded to support.
I've submitted a pull-request to .NET Core that would allow such case-less expressions, by producing a SwitchExpression
where the default value for the type of switchValue
has the same body as the default body. This approach means anything that would be surprised by a SwitchExpression
with no cases should still cope, avoiding backwards-compatibility issues. The case of there being no default either is dealt with by creating a noop expression that does nothing, so the only case that now still throws an ArgumentException
is if there is no case and no default and the type is explicitly set to something other than void
, this case being invalid under the typing rules that must obviously still be kept.
[Update: That approach was rejected, but a later pull-request was accepted, so case-less SwitchExpression
s are now allowed by .NET Core, though if and when that is adopted by other versions of .NET is another matter].
In the meantime, or if you use another version of .NET, you're best-off using a helper method like:
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.
);
}
Overloads like:
public static Expression SwitchOrDefault(Expression switchValue, Expression defaultBody, params SwitchCase[] cases)
{
return SwitchOrDefault(switchValue, defaultBody, null, (IEnumerable<SwitchCase>)cases);
}
And so on can then be added.
This results in a trimmer Expression
overall than my pull-request, because it cuts out the switch
entirely in the no-cases case and just returns the default body. If you really need to have a SwitchExpression
then you could create a similar helper method that follows the same logic as that pull-request does in creating a new SwitchCase
and then using that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With