Po przeczytaniu tego pytania na temat HNQ, zacząłem czytać o Nullable Reference Type w C # 8 i przeprowadziłem kilka eksperymentów.
Mam świadomość, że 9 razy na 10, a nawet częściej, gdy ktoś mówi: „Znalazłem błąd kompilatora!” dzieje się tak zgodnie z projektem i własnym nieporozumieniem. A ponieważ zacząłem przyglądać się tej funkcji dopiero dzisiaj, najwyraźniej nie bardzo dobrze ją rozumiem. W ten sposób, spójrzmy na ten kod:
#nullable enable
class Program
{
static void Main()
{
var s = "";
var b = s == null; // If you comment this line out, the warning on the line below disappears
var i = s.Length; // warning CS8602: Dereference of a possibly null reference
}
}
Po przeczytaniu dokumentacji, do której się powiodłem, spodziewałbym się, że s == null
linia ostrzeże mnie - w końcu s
wyraźnie nie można jej zerwać, więc porównywanie jej z null
tym nie ma sensu.
Zamiast tego otrzymuję ostrzeżenie w następnym wierszu, a ostrzeżenie mówi, że s
możliwe jest odwołanie zerowe, chociaż dla człowieka jest oczywiste, że tak nie jest.
Ponad to, ostrzeżenie jest nie wyświetlany jeśli nie porównać s
do null
.
Zrobiłem trochę googlingu i natknąłem się na problem GitHub , który okazał się dotyczyć czegoś zupełnie innego, ale w trakcie tego procesu przeprowadziłem rozmowę z współpracownikiem, która dała więcej wglądu w to zachowanie (np. „Czeki zerowe są często użytecznym sposobem mówiąca kompilatorowi, aby zresetował swoje wcześniejsze wnioskowanie na temat dopuszczalności zmiennej. ” ). Pozostało mi jednak główne pytanie bez odpowiedzi.
Zamiast tworzyć nowy problem na GitHub i potencjalnie zajmować czas niewiarygodnie zajętym współpracownikom projektu, udostępniam to społeczności.
Czy możesz mi wyjaśnić, co się dzieje i dlaczego? W szczególności dlaczego nie generuje się żadnych ostrzeżeń na s == null
linii i dlaczego mamy, CS8602
skoro nie wydaje się, że null
możliwe jest odniesienie? Jeśli wnioskowanie o zerowanie nie jest kuloodporne, jak sugeruje połączony wątek GitHub, jak może pójść nie tak? Jakie byłyby tego przykłady?
źródło
?
ponieważs
nie ma wartości zerowej. Nie staje się zerowalne, po prostu dlatego, że byliśmy wystarczająco głupi, aby go porównaćnull
.Odpowiedzi:
Jest to w rzeczywistości duplikat odpowiedzi, którą łączy @stuartd, więc nie będę tutaj wchodził w bardzo głębokie szczegóły. Ale sedno sprawy polega na tym, że nie jest to ani błąd językowy, ani błąd kompilatora, ale jego zamierzone zachowanie jest dokładnie takie, jak zaimplementowane. Śledzimy stan zerowy zmiennej. Gdy początkowo deklarujesz zmienną, stan ten to NotNull, ponieważ jawnie inicjujesz ją wartością inną niż null. Ale nie śledzimy, skąd pochodzi ten NotNull. Jest to na przykład kod równoważny:
W obu przypadkach, gdy wyraźnie przetestować
s
nanull
. Bierzemy to jako wkład do analizy przepływu, tak jak Mads odpowiedział na to pytanie: https://stackoverflow.com/a/59328672/2672518 . W wyniku tej odpowiedzi otrzymasz ostrzeżenie o powrocie. W takim przypadku odpowiedzią jest, że pojawi się ostrzeżenie, że wyrejestrowałeś potencjalnie zerowe odwołanie.Tak, to prawda. Do kompilatora. Jako ludzie możemy przyjrzeć się temu kodowi i oczywiście zrozumieć, że nie może on wygenerować wyjątku zerowego odwołania. Ale sposób, w jaki analiza nullable flow jest implementowana w kompilatorze, nie może. Omówiliśmy pewne ulepszenia w tej analizie, w których dodaliśmy dodatkowe stany w oparciu o to, skąd ta wartość pochodzi, ale zdecydowaliśmy, że zwiększyło to złożoność wdrożenia, nie dając dużego zysku, ponieważ są to jedyne miejsca, w których przydałoby się to w takich przypadkach, gdy użytkownik inicjuje zmienną o
new
wartości stałej lub stałej, a następnie sprawdza jąnull
.źródło
s == null
nie wyświetla ostrzeżenia?#nullable enable
;string s = "";s = null;
kompiluje i działa (nadal generuje ostrzeżenie) jakie są korzyści z implementacji, która pozwala przypisać wartość NULL do „niewymiennego odwołania” w włączonym kontekście adnotacji zerowej?s == null
. Być może jesteś na przykład metodą publiczną i chcesz przeprowadzić weryfikację parametrów. A może używasz biblioteki, która zawiera niepoprawne adnotacje, i dopóki nie naprawią tego błędu, musisz zająć się wartością zerową, jeśli nie została zadeklarowana. W obu przypadkach byłoby to złe doświadczenie, gdybyśmy go ostrzegali. Co do umożliwienia przypisania: adnotacje zmiennych lokalnych służą tylko do odczytu. W ogóle nie wpływają na środowisko uruchomieniowe. W rzeczywistości umieściliśmy wszystkie te ostrzeżenia w jednym kodzie błędu, abyś mógł je wyłączyć, jeśli chcesz zmniejszyć rezygnację z kodu.Z radością przyjąłem zerowane odniesienia do C # 8, gdy tylko były dostępne. Ponieważ byłem przyzwyczajony do używania notacji ReSharper [NotNull] (itp.), Zauważyłem pewne różnice między nimi.
Kompilator C # można oszukać, ale ma tendencję do bycia ostrożnym (zwykle nie zawsze).
Dla odniesienia dla przyszłych gości są to scenariusze, w których kompilator jest dość zdezorientowany (zakładam, że wszystkie te przypadki są zgodne z projektem):
Pozwala jednak użyć jakiegoś konstruktu do sprawdzenia nullability, który także pozbywa się ostrzeżenia:
lub (wciąż całkiem fajne) atrybuty (znajdź je wszystkie tutaj ):
string?
jest tylko ciągiem, toint?
staje sięNullable<int>
i zmusza kompilator do obsługi ich na zasadniczo różne sposoby. Również tutaj kompilator wybiera bezpieczną ścieżkę, zmuszając cię do określenia tego, czego oczekujesz:Rozwiązane dające ograniczenia:
Ale jeśli nie użyjemy ograniczeń i nie usuniemy „?” z danych nadal jesteśmy w stanie umieścić w nim wartości zerowe za pomocą słowa kluczowego „default”:
Ten ostatni wydaje mi się trudniejszy, ponieważ pozwala napisać niebezpieczny kod.
Mam nadzieję, że to komuś pomoże.
źródło
ThrowIfNull(s);
zapewnia, żes
nie jest zerowy), nie istnieje. Również artykuł wyjaśnia, jak obsługiwać generyczne niepozwalające na zerowanie, podczas gdy ja pokazywałem, jak można „oszukać” kompilator, mając wartość zerową, ale bez ostrzeżenia o tym.DoesNotReturnIf(bool)
.DoesNotReturnIfNull(nullable)
.