Oto kilka rozwiązań, w porządku malejącym według ogólnej dobroci:
1. Używanie default(CancellationToken)
jako wartości domyślnej:
Task DoAsync(CancellationToken ct = default(CancellationToken)) { … }
Semantycznie CancellationToken.None
byłby idealnym kandydatem na wartość domyślną, ale nie można go używać jako takiego, ponieważ nie jest to stała czasu kompilacji. default(CancellationToken)
jest następną najlepszą rzeczą, ponieważ jest stałą czasu kompilacji i oficjalnie udokumentowano jej odpowiednikCancellationToken.None
.
2. Zapewnienie przeciążenia metody bez CancellationToken
parametru:
Lub, jeśli wolisz przeciążenie metod zamiast parametrów opcjonalnych (zobacz to i to pytanie na ten temat):
Task DoAsync(CancellationToken ct) { … }
Task DoAsync() => DoAsync(CancellationToken.None);
W przypadku metod interfejsu to samo można osiągnąć za pomocą metod rozszerzających:
interface IFoo
{
Task DoAsync(CancellationToken ct);
}
static class Foo
{
public static Task DoAsync(this IFoo foo) => foo.DoAsync(CancellationToken.None);
}
Powoduje to szczuplejszy interfejs i oszczędza implementatorom jawnego pisania przeciążenia metody przekazywania.
3. Wprowadzanie wartości null dla parametru i używanie null
jako wartości domyślnej:
Task DoAsync(…, CancellationToken? ct = null)
{
… ct ?? CancellationToken.None …
}
To rozwiązanie podoba mi się przynajmniej dlatego, że typy dopuszczające wartość null mają niewielki narzut w czasie wykonywania, a odwołania do tokenu anulowania stają się bardziej szczegółowe z powodu operatora łączenia wartości null ??
.
CancellationToken.None
się czymś więcej niżdefault(CancellationToken)
.