Jak wyeliminować null w C#

Przygotowałem krótki artykuł, który pokazuje, w jaki sposób klasa Option
może wprowadzić funkcyjne koncepcje programowania do kodu w C# i zapobiec wyjątkom pustej referencji.
W tym artykule użyję biblioteki language-ext, która jest najpopularniejszą biblioteką .NET, wspierającą programowanie funkcyjne w językach obiektowych (oprócz F#).
Instalacja biblioteki language-ext
Aby pracować z biblioteką language-ext, musisz dodać odwołanie do pakietu NuGet. Można to zrobić za pomocą Menedżera pakietów NuGet w Visual Studio lub uruchamiając Install-Package LanguageExt.Core
z konsoli menedżera pakietów.
Czym jest Option?
Options są typami referencyjnymi, które zawierają pewną wartość lub jej brak. Jako przykład weźmy następującą metodę, która przeszukuje słownik i zwraca z niego wartość:
public Option<ResumeKeyword> GetKeyword(
IDictionary<string, ResumeKeyword> keywords,
string word) {
// Ensure we're searching for a lower-cased non-null word
string key = word ?? word.ToLowerInvariant() : string.Empty;
// If the keyword is present, return it
if (keywords.ContainsKey(key)) {
return keywords[key]; // Implicitly converted to Some<ResumeKeyword>
}
// No keyword found
return Option<ResumeKeyword>.None;
}
W tej metodzie, ponieważ typem zwracanym jest Option<ResumeKeyword>
, typem zwracanym będzie też Some<ResumeKeyword>
lub None
. Wynikiem nigdy nie będzie zwrócony bezpośrednio null
, więc wywołujący nie musi się martwić o nulle.
Kompilator jest w stanie przekonwertować wartość standardową na wartość Some
, ale składnia używająca typu None
nie jest idealna. Pracując z Option
zobaczymy dodatkową złożoność tego rozwiązania.
Na szczęście typ zwracany można uprościć, jeśli dodamy następującą linijkę w górnej części pliku: using static LanguageExt.Option <ResumeKeyword>;
.
To pozwala nam po prostu zwrócić None
, zamiast zwracać Option<ResumeKeyword>.None
, co znacznie łatwiej odczytać.
Teraz, gdy masz już Option
, chcesz zacząć z nim pracować. Ponieważ pracujemy z Option
, kompilator nie pozwala nam tworzyć potencjalnie błędnego kodu, takiego jak:
// Give a bump for various words in the title
foreach (var word in job.Title.Split())
{
var keyword = FindKeyword(keywordBonuses, key);
jobScore += keyword.Value; // Does not compile
}
Ten kod nie kompiluje się, ponieważ słowem kluczowym jest Option<ResumeKeyword>
zamiast ResumeKeyword
, przez co nie można uzyskać bezpośredniego dostępu do składowych ResumeKeyword
. To utrudnienie jest w rzeczywistości pozytywem, ponieważ wiemy, że FindKeyword
czasami może nie znaleźć żadnego dopasowania słowa kluczowego, co w przypadku standardowej aplikacji skutkowałoby NullReferenceException
, gdybyśmy zapomnieli dodać sprawdzenia obecności wartości null
.
Zamiast tego napiszemy:
// Give a bump for various words in the title
foreach (var word in job.Title.Split())
{
var keyword = FindKeyword(keywordBonuses, key);
jobScore += keyword.Some(k => k.Value)
.None(0);
}
Mówimy tutaj, że chcemy zwiększyć wynik o Value
z ResumeKeyword
, jeśli słowo kluczowe Some jest obecne, a jeśli jest to None, do wyniku należy dodać 0.
Sprawdzanie typów przez kompilator wymusi te reguły i upewni nas, że mamy właściwe typy, zmuszając nas do zastanowienia się, czy wartość jest obecna i zachować się prawidłowo, biorąc to pod uwagę.
W przypadku scenariuszy, w których chcesz coś zrobić tylko wtedy, gdy wartość jest obecna lub nie i nie potrzebujesz rzeczywistej wartości, możesz sprawdzić właściwości IsSome
lub IsNone
. Np.:
if (keyword.IsNone) {
Console.WriteLine($"No value defined for keyword '{word}');
}
Co z typami dopuszczającymi wartości null?
W .NET przez jakiś czas istniało pojęcie nullable types, wywoływanych przez Nullable<T>
(często kodowane jako T? myVar
).
Typ dopuszczający wartość null to sposób reprezentowania typu wartościowego jako obiektu, który może mieć wartość null. Różni się to od Option, ponieważ:
- Option działa zarówno z typami referencyjnymi, jak i wartościowymi
- Chociaż
Nullable<T>
oferujeHasValue
iValue
,Option<T>
daje wygodny sposób reagowania w określony sposób na Some i None, co widzieliśmy wyżej
Podsumowanie
Klasa Option z Language-Ext zmusza do podejmowania inteligentnych decyzji i bezpiecznej pracy przy działaniu z potencjalnie brakującymi wartościami.
Wadą tego podejścia jest to, że utrudnia to zarówno czytanie, jak i pisanie kodu,, ale w krytycznych aspektach jakości bazy kodu lub obszarach, w których często występują nulle, sensowne może być włączenie Option<T>
do kodu.
Pamiętaj też, że Option<T>
to tylko fragment możliwości oferowanych przez Language-Ext.