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.Nonebył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 CancellationTokenparametru:
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 nulljako 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.Nonesię czymś więcej niżdefault(CancellationToken).