Poważne błędy z podniesieniem / nullable konwersji z int, umożliwiając konwersję z dziesiętnych
Myślę, że to pytanie przyniesie mi natychmiastową sławę tutaj na Stack Overflow.
Załóżmy, że masz następujący typ:
// represents a decimal number with at most two decimal places after the period
struct NumberFixedPoint2
{
decimal number;
// an integer has no fractional part; can convert to this type
public static implicit operator NumberFixedPoint2(int integer)
{
return new NumberFixedPoint2 { number = integer };
}
// this type is a decimal number; can convert to System.Decimal
public static implicit operator decimal(NumberFixedPoint2 nfp2)
{
return nfp2.number;
}
/* will add more nice members later */
}
Został napisany w taki sposób, że dozwolone są tylko bezpieczne konwersje, które nie tracą precyzji. Jednak, gdy wypróbuję ten kod:
static void Main()
{
decimal bad = 2.718281828m;
NumberFixedPoint2 badNfp2 = (NumberFixedPoint2)bad;
Console.WriteLine(badNfp2);
}
Jestem zaskoczony, że to kompiluje i po uruchomieniu pisze 2
. Konwersja z int
(o wartości 2
) na NumberFixedPoint2
jest tutaj ważna. (Preferowane jest przeciążenie WriteLine
, które zajmuje System.Decimal
, gdyby ktoś się zastanawiał.)
Dlaczego, u licha, konwersja z decimal
na NumberFixedPoint2
jest dozwolona? (Nawiasem mówiąc, w powyższym kodzie, Jeśli NumberFixedPoint2
zostanie zmieniona ze struktury na klasę, nic się nie zmieni.)
Czy wiesz, czy specyfikacja języka C# mówi, że niejawna konwersja z int
do typu niestandardowego "implikuje" istnienie "bezpośredniej" jawnej konwersji z decimal
do tego typu niestandardowego?
static void Main()
{
decimal? moreBad = 7.3890560989m;
NumberFixedPoint2? moreBadNfp2 = (NumberFixedPoint2?)moreBad;
Console.WriteLine(moreBadNfp2.Value);
}
Jak widzisz, my mieć (podniósł) Nullable<>
konwersje tutaj. Ale o tak, to się skompiluje.
Po skompilowaniu w x86 "platform", kod ten wypisuje nieprzewidywalną wartość liczbową. Który z nich zmienia się od czasu do czasu. Jako przykład, pewnego razu dostałem 2289956
. To jeden poważny błąd!
Podczas kompilacji dla platformy x64 powyższy kod zawiesza aplikację z System.InvalidProgramException
z Komunikatem Common Language Runtime wykrył nieprawidłowy program. Zgodnie z dokumentacją klasy InvalidProgramException
:
Ogólnie oznacza to błąd w kompilatorze, który wygenerował program.
Czy ktoś (jak Eric Lippert, lub ktoś, kto pracował z podniesieniami konwersji w kompilatorze C#) zna przyczynę tych błędów? Na przykład, jaki jest wystarczający warunek, że nie natkniemy się na nie w naszym kodzie? Ponieważ typ NumberFixedPoint2
jest w rzeczywistości czymś, co mamy w prawdziwym kodzie (zarządzanie cudzymi pieniędzmi i rzeczy).
2 answers
Twoja druga część (używając typów nullable) wydaje się być bardzo podobna do tego znanego błędu w obecnym kompilatorze. z odpowiedzi na problem Connect:
Chociaż obecnie nie mamy planów rozwiązania tego problemu w następnej wersji Visual Studio, planujemy zbadać poprawkę w Roslyn
W związku z tym, mam nadzieję, że ten błąd zostanie poprawiony w przyszłym wydaniu Visual Studio i kompilatorów.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-20 19:07:41
Odpowiadam na pierwszą część pytania na początek. (Sugeruję, że druga część powinna być osobnym pytaniem; bardziej prawdopodobne jest, że będzie to błąd.)
Istnieje tylko Jawna konwersja Z decimal
na int
, ale ta konwersja jest niejawnie wywoływana w Twoim kodzie. Konwersja odbywa się w tym IL:
IL_0010: stloc.0
IL_0011: ldloc.0
IL_0012: call int32 [mscorlib]System.Decimal::op_Explicit(valuetype [mscorlib]System.Decimal)
IL_0017: call valuetype NumberFixedPoint2 NumberFixedPoint2::op_Implicit(int32)
Uważam, że to jest poprawne zachowanie zgodnie ze specyfikacją, mimo że zaskakujące1. Przejdźmy do sekcji 6.4.5 specyfikacji C # 4 (User-Defined Explicit Conversions). Nie będę kopiował całego tekstu, bo byłby nudny - tylko jakie są istotne wyniki w naszym przypadku. Podobnie Nie będę używał indeksów dolnych, ponieważ nie działają one dobrze z czcionką kodu:)
- określić typy
S0
iT0
:S0
jestdecimal
iT0
jestNumberFixedPoint2
. - Znajdź zbiór typów,
D
, z których będą brane pod uwagę użyte zdefiniowane operatory konwersji: just{ decimal, NumberFixedPoint2 }
- Znajdź zbiór odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podnoszonych,
U
.decimal
obejmujeint
(Sekcja 6.4.3) ponieważ istnieje standardowa konwersja implicit zint
nadecimal
. Tak więc operator jawnej konwersji jest wU
i jest rzeczywiście jedynym członkiemU
- Znajdź najbardziej konkretny typ źródła,
Sx
, z operatorów wU
- operator nie konwertuje z
S
(decimal
) więc pierwsza kula jest out - operator nie konwertuje z typu, który obejmuje
S
(decimal
int
, nie odwrotnie), więc drugi pocisk wypadł - zostaje trzecia kula, która mówi o" najbardziej obejmującym typie " - Cóż, mamy tylko jeden typ, więc jest w porządku:
Sx
jestint
.
- operator nie konwertuje z
- Znajdź najbardziej konkretny typ docelowy,
Tx
, z operatorów wU
- operator przechodzi prosto do
NumberFixedPoint2
więcTx
jestNumberFixedPoint2
.
- operator przechodzi prosto do
- Znajdź najbardziej konkretny operator konwersji:
-
U
zawiera dokładnie jeden operator, który rzeczywiście przekształca się zSx
naTx
, więc jest to najbardziej konkretny operator
-
- na koniec zastosuj konwersję:
-
Jeśli
S
Nie jestSx
, to wykonywana jest standardowa konwersja jawna zS
naSx
. (czylidecimal
doint
.) - wywoływany jest najbardziej konkretny operator konwersji zdefiniowany przez użytkownika (Twój operator)
-
T
jestTx
więc nie ma potrzeby konwersji w trzecim pocisku
-
Jeśli
Linia pogrubiona jest bitem, który potwierdza, że standardowa Jawna konwersja jest naprawdę wykonalna, gdy tylko Jawna konwersja z innego typu jest faktycznie określona.
1 przynajmniej mnie to zaskoczyło. Nie jestem świadoma tego wcześniej.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-20 19:15:11