2015-02-06 2 views
11

Ich habe versucht, einen Schalter Ausdruck mit System.Linq.Expressions zu erstellen:Schalter ohne Fälle (aber mit Standard) in 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); 

aber in der letzten Zeile bekomme ich ein Argument:

Nicht leere Sammlung erforderlich. Parametername: Fälle

Was ist der Grund für diese Ausnahme? Kann das ein Fehler in Expression.Switch(…) sein?

In einem C# ein Schalter mit "default" Teil ist nur dann richtig:

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

UPD: Ich habe an issue habe den CoreFX Repo auf GitHub

+2

Was ist das Ziel einer solchen Konstruktion? 'switch' mit' default' und kein 'case' würde einfach' default' ausführen –

+0

Für mich sieht der Switch ohne Gehäuse ziemlich bedeutungslos aus, also denke ich, dass diese Ausnahme vernünftig ist. –

+0

Ja, die C# -Spezifikation besagt, dass ein Switch-Block aus null oder mehr Switch-Sektionen besteht; aber das bedeutet nicht, dass ein Schalterausdruck mit der C# Spezifikation übereinstimmen muss. Da Sie den Ausdruck zur Laufzeit erstellen, könnten Sie zur Umgehung einfach eine "Expression.SwitchCase" mit einem Wert hinzufügen, der! = Switch value ist; oder fügen Sie den Hauptteil des Standardfalls als einen Switch Case mit Wert = switch-Wert hinzu. – sloth

Antwort

6

Es wird keine vollständige Analogie zwischen C# switch und SwitchExpression. In der anderen Richtung, die Ansicht, dass man haben kann:

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 

Hier ist die SwitchExpression gibt einen Wert, der etwas switch nicht tun kann.

Also, nur in der Lage, etwas mit SwitchExpression zu tun, bedeutet nicht, Sie es mit einem switch tun können, so auch gibt es keinen Grund anzunehmen, dass mit einem switch etwas in der Lage zu tun, bedeutet, dass Sie es mit einem SwitchExpression tun können .

Das sagte, ich sehe keinen guten Grund, warum SwitchExpression wurde auf diese Weise festgelegt, außer vielleicht, dass es den Fall vereinfacht, wo ein Ausdruck hat keine Fälle und keine Standard-Körper. Das heißt, ich denke, dass dies wahrscheinlich nur eine Frage des Ausdrucks war, der im Allgemeinen dazu gedacht ist, mehrere Fälle zu haben, und das war es, für dessen Unterstützung er kodifiziert wurde.

Ich habe submitted a pull-request to .NET Core, die einem solchen Fall lose Ausdrücke erlauben würde, durch eine SwitchExpression produzieren, wo der Standardwert für die Art der switchValue den gleichen Körper als Standard-Körper hat. Dieser Ansatz bedeutet, dass alles, was durch eine SwitchExpression überrascht werden würde, sollte keine Fälle noch bewältigen sollte, um Rückwärtskompatibilitätsprobleme zu vermeiden. Der Fall, dass es keinen Standardwert gibt, wird durch Erstellen eines Noop-Ausdrucks behandelt, der nichts tut. Der einzige Fall, der jetzt noch ArgumentException lautet, ist und kein Standardwert und, auf den der Typ explizit festgelegt ist etwas anderes als void, dieser Fall ist unter den Tippregeln ungültig, die offensichtlich noch behalten werden müssen.

[Update: Dieser Ansatz wurde abgelehnt, aber a later pull-request wurde angenommen, so fall weniger SwitchExpression s werden nun von .NET-Core erlaubt, obwohl, ob und wann die von anderen Versionen von .NET ist eine andere Sache angenommen wird].

In der Zwischenzeit, oder wenn Sie eine andere Version von verwenden.NET, sind Sie am besten-off eine Hilfsmethode verwenden wie:

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. 
); 
} 

Überlastungen wie:

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

und so weiter kann dann hinzugefügt werden.

Dies führt zu einem Trimmer Expression insgesamt als meine Pull-Anfrage, weil es die switch ganz im Fall keine Fälle ausschließt und nur die Standard-Körper zurückgibt. Wenn Sie wirklich benötigen, um eine SwitchExpression haben, dann könnten Sie eine ähnliche Hilfsmethode erstellen, die der gleichen Logik wie diese Pull-Anforderung bei der Erstellung einer neuen SwitchCase folgt und dann verwenden.