Próbuję stworzyć typ podobny do Rust'a Result
lub Haskella Either
i mam tak daleko:
public struct Result<TResult, TError>
where TResult : notnull
where TError : notnull
{
private readonly OneOf<TResult, TError> Value;
public Result(TResult result) => Value = result;
public Result(TError error) => Value = error;
public static implicit operator Result<TResult, TError>(TResult result)
=> new Result<TResult, TError>(result);
public static implicit operator Result<TResult, TError>(TError error)
=> new Result<TResult, TError>(error);
public void Deconstruct(out TResult? result, out TError? error)
{
result = (Value.IsT0) ? Value.AsT0 : (TResult?)null;
error = (Value.IsT1) ? Value.AsT1 : (TError?)null;
}
}
Biorąc pod uwagę, że parametry obu typów są ograniczone notnull
, dlaczego narzeka (wszędzie tam, gdzie jest parametr typu ze ?
znakiem null ), że:
Parametr typu dopuszczającego wartość zerową musi być znany jako typ wartości lub typ odwołania nie dopuszczający wartości zerowej. Rozważ dodanie ograniczenia „klasy”, „struktury” lub typu.
?
Używam C # 8 na .NET Core 3 z włączonymi zerowymi typami referencji.
c#
generics
type-constraints
c#-8.0
nullable-reference-types
Diamentowe buty
źródło
źródło
Odpowiedzi:
Zasadniczo prosisz o coś, czego nie można przedstawić w IL. Typy dopuszczające wartości zerowe i typy referencyjne dopuszczające wartości zerowe są bardzo różnymi zwierzętami i chociaż wyglądają podobnie w kodzie źródłowym, IL jest zupełnie inna. Wersja dopuszczająca wartości zerowe typu wartości
T
jest innym typem (Nullable<T>
), natomiast wersja dopuszczająca wartości zerowe typu referencyjnegoT
jest tego samego typu, a atrybuty mówią kompilatorowi, czego się spodziewać.Rozważ ten prostszy przykład:
To nieważne z tego samego powodu.
Jeśli ograniczymy się
T
do bycia strukturą, wówczas wygenerowana IL dlaGetNullValue
metody miałaby typ zwracanyNullable<T>
.Jeśli ograniczymy się
T
do tego, że nie ma wartości zerowej, wówczas generowana dla tejGetNullValue
metody IL miałaby typ zwracanyT
, ale z atrybutem dla aspektu zerowalności.Kompilator nie może wygenerować IL dla metody, która ma typ zwracany jednocześnie
T
iNullable<T>
jednocześnie.Zasadniczo wynika to z faktu, że typy zerowalne nie są w ogóle koncepcją CLR - to tylko magia kompilatora, która pomaga wyrazić zamiary w kodzie i zmusić kompilator do sprawdzenia podczas kompilacji.
Komunikat o błędzie nie jest jednak tak jasny, jak może być.
T
wiadomo, że jest „typem wartości lub niedopuszczalnym typem odniesienia”. Bardziej precyzyjnym (ale znacznie bardziej tekstowym) komunikatem o błędzie byłoby:W tym momencie błąd miałby zasadne zastosowanie do naszego kodu - parametr typu nie jest „znany jako typ wartości” i nie jest znany jako „typ zerowy, który nie ma wartości zerowej”. Jest znany jako jeden z dwóch, ale kompilator musi wiedzieć, który .
źródło
Nullable<T>
jest specjalnym typem, którego nie możesz zrobić sam. I jest jeszcze jeden bonus za to, jak boks jest wykonywany z zerowanymi typami.Powodem ostrzeżenia jest wyjaśnione w rozdziale
The issue with T?
o wypróbować Nullable typów referencyjnych . Krótko mówiąc, jeśli używaszT?
, musisz określić, czy typ jest klasą, czy strukturą. Możesz w końcu stworzyć dwa typy dla każdej sprawy.Głębszy problem polega na tym, że użycie jednego typu do zaimplementowania wyniku i utrzymania zarówno wartości sukcesu, jak i błędu przywraca te same problemy, które wynik miał rozwiązać, i kilka innych.
Wynik (i oba) w F #
Punktem wyjścia powinien być typ wyniku F # i dyskryminowane związki. W końcu działa to już w .NET.
Typ wyniku w F # to:
Same typy niosą tylko to, czego potrzebują.
DU w F # pozwalają na wyczerpujące dopasowanie wzorca bez konieczności zerowania:
Emulując to w C # 8
Niestety C # 8 nie ma jeszcze DU, są zaplanowane na C # 9. W C # 8 możemy to naśladować, ale tracimy wyczerpujące dopasowanie:
I użyj tego:
Bez wyczerpującego dopasowania wzorca musimy dodać tę domyślną klauzulę, aby uniknąć ostrzeżeń kompilatora.
Wciąż szukam sposobu na wyczerpujące dopasowanie bez wprowadzania martwych wartości, nawet jeśli są one tylko Opcją.
Opcja / Może
Tworzenie klasy Option przy użyciu wyczerpującego dopasowania jest prostsze:
Które mogą być używane z:
źródło