2015-02-06 8 views
11

Próbowałem stworzyć wyrażenie switch z System.Linq.Expressions:Przełącznik bez przypadkach (ale z standardowe) 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); 

ale w ostatnim wierszu otrzymuję ArgumentException:

Wymagana niepusta kolekcja. Nazwa parametru: przypadki

Jaki jest powód tego wyjątku? Może to być błąd w Expression.Switch(…)?

W języku C# przełącznik z "default" tylko częściowo jest poprawne:

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

UPD: I złożyli an issue do repo CoreFX na GitHub

+2

Jaki jest cel takiej konstrukcji? 'switch' z' default' i bez 'case' będzie po prostu wykonywać' default' –

+0

Dla mnie przełączanie bez przypadków wygląda całkiem bez znaczenia, więc myślę, że ten wyjątek jest rozsądny. –

+0

Tak, specyfikacja C# mówi, że blok przełącznika ma zero lub więcej sekcji przełączających; ale to nie znaczy, że wyrażenie przełączające musi konfabulować do specyfikacji C#. Ponieważ tworzysz wyrażenie w czasie wykonywania, możesz, jako obejście, dodać po prostu wyrażenie "Expression.SwitchCase" z wartością! = Wartość przełączania; lub dodaj treść domyślnego przypadku jako przypadku przełącznika o wartości = wartość przełączania. – sloth

Odpowiedz

6

Nie jest pełna analogia między C# 's switch i SwitchExpression. W innym kierunku, uważają, że można mieć:

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 

Tutaj SwitchExpression zwraca wartość, która jest czymś switch nie może zrobić.

Więc, tak jak jest w stanie zrobić coś z SwitchExpression nie oznacza, że ​​można zrobić z switch, dlatego też nie ma powodu, aby przypuszczać, że jest w stanie zrobić coś z switch oznacza, że ​​można to zrobić z SwitchExpression .

To powiedziawszy, nie widzę żadnego powodu, dla którego SwitchExpression został ustawiony w ten sposób, z wyjątkiem być może, że upraszcza przypadek, w którym wyrażenie nie ma żadnych przypadków i brak domyślnego ciała. To powiedziawszy, myślę, że to prawdopodobnie kwestia, że ​​wyrażenie ma na ogół mieć wiele przypadków, i to było to, co zostało zakodowane w celu wsparcia.

mam submitted a pull-request to .NET Core które pozwoliłyby takich wyrażeń przypadków mniej, produkując SwitchExpression gdzie wartość domyślną dla danego typu switchValue ma to samo ciało jako domyślną ciała. Podejście to oznacza wszystko, co mogłoby być zaskoczone przez SwitchExpression, ale żadne przypadki nie powinny w dalszym ciągu sobie z tym poradzić, unikając problemów ze zgodnością wsteczną. Przypadek braku wartości domyślnej jest rozwiązywany przez utworzenie wyrażenia noop, które nic nie robi, więc jedyny przypadek, który teraz nadal powoduje wyrzucenie ArgumentException, jest taki, że nie ma domyślnego typu i bez domyślnego ustawienia. Typ jest jawnie ustawiony na coś innego niż void, ta sprawa jest nieważna zgodnie z regułami pisania, które oczywiście muszą być nadal przechowywane.

[Aktualizacja: To podejście zostało odrzucone, ale a later pull-request została przyjęta, więc mniej SwitchExpression case-ów są obecnie dopuszczone przez .NET Rdzenia, chociaż wtedy, gdy ten zostanie przyjęty przez inne wersje NET to inna sprawa].

Tymczasem lub jeśli używasz innej wersji.NET, jesteś najlepiej wyłączyć za pomocą metody pomocnika jak:

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

Przeciążenia jak:

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

i tak dalej mogą być następnie dodaje.

To powoduje, że przycinarka Expression jest generalnie krótsza niż moje żądanie ściągnięcia, ponieważ całkowicie wyłącza numer switch w przypadku braku spraw i po prostu zwraca domyślną treść. Jeśli naprawdę potrzebujesz , musisz mieć SwitchExpression, a następnie możesz stworzyć podobną metodę pomocniczą, która będzie zgodna z tą samą logiką, co żądanie pull-in w tworzeniu nowego SwitchCase, a następnie z użyciem tego.