Co to jest ?[]? składnia w C #?

85

Podczas gdy studiowałem delegata, który jest w rzeczywistości klasą abstrakcyjną Delegate.cs, zobaczyłem następującą metodę, której nie rozumiem

  • Dlaczego wartość zwracana wykorzystuje, ?mimo że jest to już typ referencyjny ( klasa )
  • ?[]? znaczenie w parametrze

Czy możesz wytłumaczyć?

public static Delegate? Combine(params Delegate?[]? delegates)
{
    if (delegates == null || delegates.Length == 0)
        return null;

    Delegate? d = delegates[0];
    for (int i = 1; i < delegates.Length; i++)
        d = Combine(d, delegates[i]);

    return d;
}
snr - Przywróć Monikę
źródło
23
Czy nie jest to tablica zerowalna, która może zawierać wartości zerowalne?
Cid
3
c # 8, możesz teraz określić, że zmienna obiektowa nie może mieć wartości null. Jeśli odwrócić tej flagi kompilatora, trzeba określić każdą zmienną, która jest wolno zerowa.
Jeremy Lakeman

Odpowiedzi:

66

Wyjaśnienie krok po kroku:

params Delegate?[] delegates - To tablica zerowalnych Delegate

params Delegate?[]? delegates - Cała tablica może być zerowalna

Ponieważ każdy parametr jest tego samego typu, Delegate?a zwracany jest indeks Delegate?[]?tablicy, ma sens, że typem zwracanym jest Delegate?inaczej, kompilator zwróciłby błąd tak, jakbyś się zatrzymywał intz metody zwracającej ciąg.

Możesz na przykład zmienić kod, aby zwracał taki Delegatetyp:

public static Delegate Combine(params Delegate?[]? delegates)
{
    Delegate defaulDelegate = // someDelegate here
    if (delegates == null || delegates.Length == 0)
        return defaulDelegate;

    Delegate d = delegates[0] ?? defaulDelegate;
    for (int i = 1; i < delegates.Length; i++)
        d = Combine(d, delegates[i]);

    return d;
}
Athanasios Kataras
źródło
4
co z moim pierwszym pytaniem? Dlaczego wartość zwracana wykorzystuje, ?mimo że jest to już typ referencyjny (klasa)
snr - Przywróć Monikę
2
Ponieważ zwracasz d, która jest delegatem? rodzaj. A także null lub Delegate w zależności od parametrów
Athanasios Kataras
2
@ snr Zwracana wartość wykorzystuje ?dokładnie ten sam powód, dla którego wartość argumentu używa ?. Jeśli zrozumiesz to drugie, automatycznie zrozumiesz pierwsze. Ponieważ metoda może zwracać null , podobnie jak parametr może przyjmować null . Oznacza to, że oba mogą zawierać wartość null .
GSerg
2
Dotknąłem tego. Właśnie odpowiedziałem na to jako drugą część z przykładem. Since each parameter is of the type Delegate? and you return an index of the Delegate?[]? array, then it makes sense that the return type is Delegate?
Athanasios Kataras
2
@ snr: Dlaczego używana jest wartość zwracana? chociaż jest to już typ referencyjny (klasa) (1) Może to być nawyk lub pozostałość po refaktoryzacji, jeśli struktury są również używane w podobnym kodzie (2) W języku C # 8 typy referencyjne mogą być niedozwolone z natury nullable (co wymaga jawnej nullability ( 3) Foo?ma dobrą HasValuewłaściwość, podczas gdy Foonależy ją == nullsprawdzić (4) Informuje programistów, którzy czytają sygnaturę metody, że wartości null są możliwe, należy się spodziewać, i poprawny wynik. (5) Kod wygląda na napisany przez kogoś, kto bardzo lubi nullabness i jasno o tym mówi
Flater
25

Typy zerowalne odniesienia są nowe w C # 8.0, wcześniej nie istniały.

To kwestia dokumentacji i sposobu generowania ostrzeżeń w czasie kompilacji.

Wyjątek „obiekt nieprzypisany do instancji obiektu” jest bardzo często spotykany. Jest to jednak wyjątek czasu wykonywania, który można częściowo wykryć już w czasie kompilacji.

Aby uzyskać regulację Delegate d, zawsze możesz zadzwonić

 d.Invoke();

co oznacza, że ​​możesz to kodować, w czasie kompilacji nic się nie wydarzy. Może to powodować wyjątki w czasie wykonywania.

Chociaż nowy Delegate? pkodeks

 p.Invoke();

wyświetli ostrzeżenie kompilatora. CS8602: Dereference of a possibly null reference chyba że napiszesz:

 p?.Invoke();

co oznacza, dzwoń tylko wtedy, gdy nie ma wartości null.

Więc dokumentujesz, że zmienna może zawierać null lub nie. Wcześniej wyświetla ostrzeżenia i może uniknąć wielu testów na wartość NULL. To samo, co masz dla int i int ?. Wiesz na pewno, że jeden nie jest zerowy - i wiesz, jak przekonwertować jeden na drugi.

Holger
źródło
z zastrzeżeniem, którego na pewno nie znasz, które Delegatenie jest zerowe. Udajesz, że wiesz na pewno (co w większości przypadków jest wystarczająco dobre).
Tim Pohlmann,
@TimPohlmann Oprócz int i = null jest niemożliwe, również łańcuch s = null jest niemożliwy (w C # 8 z tą zmianą zerwania). To coś więcej niż udawanie. Dla kompatybilności wstecznej został obniżony do ostrzeżenia. Jeśli zaktualizujesz ostrzeżenie do błędu, na pewno wiesz, że nie jest ono zerowe.
Holger,
4
Ostatnio sprawdziłem, że NRT nie są w 100% odporne na pociski. Istnieją pewne przypadki brzegowe, których kompilator nie może wykryć.
Tim Pohlmann
Tak, ale jest to to samo, co ostrzeżenia „zmienna nie została zainicjowana” lub „nie wszystkie ścieżki kodu zwracają wartość”. Są to funkcje w 100% czasu kompilacji, bez wykrywania w czasie wykonywania. W przeciwieństwie do int ?, myślę, że nie dodają dodatkowej pamięci do przechowywania dodatkowych informacji. Powiedzmy, że jest tak niezawodny jak inteligencja. Całkiem dobre.
Holger
1
Jeśli masz int, nigdy nie może być zerowy. Jeśli masz Delegate, może być zerowy (z różnych powodów, np. Refleksji). Zazwyczaj można bezpiecznie założyć, że Delegate(w C # 8 z włączoną NRT) nie ma wartości zerowej, ale nigdy nie wiadomo na pewno (gdzie intna pewno wiemy).
Tim Pohlmann,
5

W C # 8 należy wyraźnie oznaczyć typy referencji jako zerowalne.

Domyślnie typy te nie mogą zawierać wartości null, podobnie jak typy wartości. Chociaż to nie zmienia sposobu działania rzeczy pod maską, narzędzie do sprawdzania typów będzie wymagać ręcznego wykonania tej czynności.

Podany kod jest refaktoryzowany do pracy z C # 8, ale nie korzysta z tej nowej funkcji.

public static Delegate? Combine(params Delegate?[]? delegates)
{
    // ...[]? delegates - is not null-safe, so check for null and emptiness
    if (delegates == null || delegates.Length == 0)
        return null;

    // Delegate? d - is not null-safe too
    Delegate? d = delegates[0];
    for (int i = 1; i < delegates.Length; i++)
        d = Combine(d, delegates[i]);

    return d;
}

Oto przykład zaktualizowanego kodu (nie działa, tylko pomysł) wykorzystującego tę funkcję. Uratowało to nas przed zerowym sprawdzeniem i nieco uprościło tę metodę.

public static Delegate? Combine(params Delegate[] delegates)
{
    // `...[] delegates` - is null-safe, so just check if array is empty
    if (delegates.Length == 0) return null;

    // `d` - is null-safe too, since we know for sure `delegates` is both not null and not empty
    Delegate d = delegates[0];

    for (int i = 1; i < delegates.Length; i++)
        // then here is a problem if `Combine` returns nullable
        // probably, we can add some null-checks here OR mark `d` as nullable
        d = Combine(d, delegates[i]);

    return d;
}
amankkg
źródło
2
those types are not able to contain null, zwróć uwagę, że generuje ostrzeżenie kompilatora , a nie błąd. Kod będzie działał poprawnie (choć może generować wyjątki NullReferenceExceptions). Odbywa się to z myślą o kompatybilności wstecznej.
JAD