Po migracji projektu z VS2013 do VS2015 projekt nie jest już kompilowany. Błąd kompilacji występuje w następującej instrukcji LINQ:
static void Main(string[] args)
{
decimal a, b;
IEnumerable<dynamic> array = new string[] { "10", "20", "30" };
var result = (from v in array
where decimal.TryParse(v, out a) && decimal.TryParse("15", out b) && a <= b // Error here
orderby decimal.Parse(v)
select v).ToArray();
}
Kompilator zwraca błąd:
Błąd CS0165 Użycie nieprzypisanej zmiennej lokalnej „b”
Co powoduje ten problem? Czy można to naprawić za pomocą ustawień kompilatora?
b
po przypisaniu go za pomocąout
parametru.out
argumentach. Czy toTryParse
zwróciło wartość dopuszczającą wartość null (lub równoważną).where (a = decimal.TryParse(v)).HasValue && (b = decimal.TryParse(v)).HasValue && a <= b
wygląda znacznie lepiejdecimal a, b; var q = decimal.TryParse((dynamic)"10", out a) && decimal.TryParse("15", out b) && a <= b;
. Mam otwarte błąd Roslyn podnoszenie tego.Odpowiedzi:
Wydaje mi się, że to błąd kompilatora. A przynajmniej tak. Chociaż wyrażenia
decimal.TryParse(v, out a)
idecimal.TryParse(v, out b)
są obliczane dynamicznie, spodziewałem się, że kompilator nadal zrozumie, że zanim dotrzea <= b
, obaa
ib
zostaną ostatecznie przypisane. Nawet z dziwactwami, które możesz wymyślić podczas dynamicznego pisania, spodziewałbym się, że kiedykolwiek ocenię tylkoa <= b
po ocenie obuTryParse
wywołań.Jednak okazuje się, że za pośrednictwem operatora i nawrócenia trudne, jest to całkowicie możliwe, aby mieć wyraz
A && B && C
która oceniaA
iC
, ale nieB
- Jeśli jesteś przebiegły dość. Zobacz raport o błędzie Roslyn, aby zapoznać się z genialnym przykładem Neala Gaftera.Wykonanie tej pracy
dynamic
jest jeszcze trudniejsze - semantyka stosowana, gdy operandy są dynamiczne, jest trudniejsza do opisania, ponieważ aby wykonać rozwiązanie przeciążenia, musisz ocenić operandy, aby dowiedzieć się, jakie typy są zaangażowane, co może być sprzeczne z intuicją. Jednak ponownie Neal wymyślił przykład, który pokazuje, że błąd kompilatora jest wymagany ... to nie jest błąd, to naprawa błędu . Ogromne uznanie dla Neala za udowodnienie tego.Nie, ale istnieją alternatywy, które pozwalają uniknąć błędu.
Po pierwsze, możesz zatrzymać dynamikę - jeśli wiesz, że będziesz używać tylko łańcuchów, możesz użyć
IEnumerable<string>
lub nadać zmiennej zakresuv
typstring
(tjfrom string v in array
.). To byłaby moja preferowana opcja.Jeśli naprawdę chcesz zachować dynamikę, po prostu podaj
b
wartość na początek:To nie zaszkodzi - wiemy, że tak naprawdę twoja dynamiczna ocena nie zrobi nic szalonego, więc nadal będziesz przypisywać wartość
b
przed jej użyciem, sprawiając, że początkowa wartość będzie nieistotna.Dodatkowo wydaje się, że dodawanie nawiasów też działa:
To zmienia punkt, w którym wyzwalane są różne elementy rozwiązania przeciążenia, i sprawia, że kompilator jest zadowolony.
Pozostaje jeszcze jedna kwestia - zasady specyfikacji dotyczące określonego przypisania do
&&
operatora muszą zostać wyjaśnione, aby stwierdzić, że mają one zastosowanie tylko wtedy, gdy&&
operator jest używany w jego „zwykłej” implementacji z dwomabool
operandami. Spróbuję się upewnić, że zostanie to naprawione dla następnego standardu ECMA.źródło
IEnumerable<string>
lub dodawanie nawiasów zadziałało. Teraz kompilator kompiluje się bez błędów.decimal a, b = 0m;
może usunąć błąd, alea <= b
zawsze będzie używać0m
, ponieważ wartość wyjściowa nie została jeszcze obliczona.decimal? TryParseDecimal(string txt)
może być też rozwiązaniemb
że może nie być przypisana”; Wiem, że to nieprawidłowe rozumowanie, ale wyjaśnia, dlaczego nawiasy to naprawiają ...Wygląda na to, że jest to błąd lub przynajmniej regresja w kompilatorze Roslyn. Zgłoszono następujący błąd, aby go śledzić:
W międzyczasie doskonała odpowiedź Jona wymaga kilku obejść.
źródło
Ponieważ tak ciężko uczył mnie w raporcie o błędzie, spróbuję sam to wyjaśnić.
Wyobraź sobie, że
T
jest to typ zdefiniowany przez użytkownika z niejawnym rzutowaniem nabool
naprzemiennie międzyfalse
itrue
, zaczynając odfalse
. O ile kompilator wie,dynamic
pierwszy argument do pierwszego&&
może mieć wartość tego typu, więc musi być pesymistyczny.Jeśli więc pozwoli na kompilację kodu, może się to zdarzyć:
&&
, wykonuje następujące czynności:T
- niejawnie rzut dobool
.false
, więc nie musimy oceniać drugiego argumentu.&&
oceny należy traktować jako pierwszy argument. (Nie, niefalse
, z jakiegoś powodu.)&&
, wykonuje następujące czynności:T
- niejawnie rzut dobool
.true
, więc oceń drugi argument.b
nie przydzielono.Mówiąc konkretnie, w skrócie, istnieją specjalne reguły „określonego przypisania”, które pozwalają nam powiedzieć nie tylko, czy zmienna jest „definitywnie przypisana”, czy „nieprzypisana ostatecznie”, ale także czy jest „zdecydowanie przypisana po
false
wyrażeniu” lub „zdecydowanie przypisane potrue
stwierdzeniu ”.Istnieją one tak, że podczas pracy z
&&
i||
(i!
i??
i?:
) kompilator może zbadać, czy zmienne mogą być przypisane do poszczególnych gałęzi złożonego wyrażenia boolowskiego.Jednak działają one tylko wtedy, gdy typy wyrażeń pozostają logiczne . Gdy część wyrażenia jest
dynamic
(lub nie jest logicznym typem statycznym), nie możemy już wiarygodnie powiedzieć, że wyrażenie to jesttrue
lubfalse
- następnym razem, gdy rzucimy je,bool
aby zdecydować, którą gałąź wziąć, mogło zmienić zdanie.Aktualizacja: to zostało już rozwiązane i udokumentowane :
źródło
out
do metody, która maref
. Z przyjemnością to zrobi i przypisze zmienne bez zmiany wartości.false
/true
w przeciwieństwie do niejawnego operatora rzutowania? Lokalnie wywołaimplicit operator bool
pierwszy argument, następnie wywoła drugi operand, wywołaoperator false
pierwszy operand, a następnie ponownieimplicit operator bool
pierwszy operand . To nie ma dla mnie sensu, pierwszy operand powinien kiedyś sprowadzać się do wartości logicznej, prawda?dynamic
przykuta&&
sprawa? Widziałem, że w zasadzie idę (1) ocenić pierwszy argument (2) użyć niejawnego rzutowania, aby sprawdzić, czy mogę zwrzeć (3) nie mogę, więc pomiń drugi argument (4) teraz znam oba typy, ja widzę najlepiej&&
zdefiniowany przez użytkownika&
(5) operator wywołaniafalse
na pierwszym argumencie, aby sprawdzić, czy mogę zwrzeć (6) Mogę (ponieważfalse
i sięimplicit bool
nie zgadzam), więc wynikiem jest pierwszy argument ... a następnie następny&&
, (7) użyj niejawnego rzutowania, aby sprawdzić, czy mogę zwrzeć (ponownie).To nie jest błąd. Zobacz https://github.com/dotnet/roslyn/issues/4509#issuecomment-130872713, aby zobaczyć przykład, jak dynamiczne wyrażenie tego formularza może pozostawić taką zmienną out nieprzypisaną.
źródło