Inne odpowiedzi dały świetne wyjaśnienia, dlaczego opcjonalny parametr nie może być wyrażeniem dynamicznym. Ale żeby przypomnieć, parametry domyślne zachowują się jak stałe czasu kompilacji. Oznacza to, że kompilator musi być w stanie je ocenić i udzielić odpowiedzi. Są ludzie, którzy chcą, aby C # dodawał obsługę kompilatora oceniającego dynamiczne wyrażenia podczas napotykania stałych deklaracji - ten rodzaj funkcji byłby powiązany z oznaczaniem metod jako „czystych”, ale to nie jest obecnie rzeczywistością i może nigdy nie być.
Jedną z alternatyw dla użycia domyślnego parametru C # dla takiej metody byłoby użycie wzorca, którego przykładem jest XmlReaderSettings
. W tym wzorcu zdefiniuj klasę z konstruktorem bez parametrów i publicznie zapisywalnymi właściwościami. Następnie zamień wszystkie opcje na wartości domyślne w swojej metodzie na obiekt tego typu. Nawet ustaw ten obiekt jako opcjonalny, określając null
dla niego wartość domyślną . Na przykład:
public class FooSettings
{
public TimeSpan Span { get; set; } = TimeSpan.FromSeconds(2);
// I imagine that if you had a heavyweight default
// thing you’d want to avoid instantiating it right away
// because the caller might override that parameter. So, be
// lazy! (Or just directly store a factory lambda with Func<IThing>).
Lazy<IThing> thing = new Lazy<IThing>(() => new FatThing());
public IThing Thing
{
get { return thing.Value; }
set { thing = new Lazy<IThing>(() => value); }
}
// Another cool thing about this pattern is that you can
// add additional optional parameters in the future without
// even breaking ABI.
//bool FutureThing { get; set; } = true;
// You can even run very complicated code to populate properties
// if you cannot use a property initialization expression.
//public FooSettings() { }
}
public class Bar
{
public void Foo(FooSettings settings = null)
{
// Allow the caller to use *all* the defaults easily.
settings = settings ?? new FooSettings();
Console.WriteLine(settings.Span);
}
}
Aby wywołać, użyj tej dziwnej składni do tworzenia wystąpienia i przypisywania właściwości w jednym wyrażeniu:
bar.Foo(); // 00:00:02
bar.Foo(new FooSettings { Span = TimeSpan.FromDays(1), }); // 1.00:00:00
bar.Foo(new FooSettings { Thing = new MyCustomThing(), }); // 00:00:02
Wady
To naprawdę ciężkie podejście do rozwiązania tego problemu. Jeśli piszesz szybki i brudny interfejsu wewnętrznego i dokonywania TimeSpan
zerowalne i leczenia NULL jak żądanej wartości domyślnej będzie działać dobrze, że zamiast robić.
Ponadto, jeśli masz dużą liczbę parametrów lub wywołujesz metodę w ścisłej pętli, będzie to miało narzut związany z tworzeniem instancji klas. Oczywiście wywołanie takiej metody w ciasnej pętli może być naturalne, a nawet bardzo łatwe do ponownego wykorzystania instancji FooSettings
obiektu.
Korzyści
Jak wspomniałem w komentarzu w przykładzie, myślę, że ten wzorzec jest świetny dla publicznych interfejsów API. Dodanie nowych właściwości do klasy jest niezepsutą zmianą ABI, więc możesz dodawać nowe parametry opcjonalne bez zmiany podpisu metody przy użyciu tego wzorca - dając ostatnio skompilowanemu kodowi więcej opcji, jednocześnie kontynuując obsługę starego skompilowanego kodu bez dodatkowej pracy .
Ponadto, ponieważ wbudowane w C # domyślne parametry metod są traktowane jako stałe kompilacji i umieszczane na stronie wywołania, parametry domyślne będą używane przez kod dopiero po ponownej kompilacji. Tworząc wystąpienie obiektu ustawień, obiekt wywołujący dynamicznie ładuje wartości domyślne podczas wywoływania metody. Oznacza to, że możesz zaktualizować ustawienia domyślne, po prostu zmieniając klasę ustawień. Dlatego ten wzorzec umożliwia zmianę wartości domyślnych bez konieczności ponownej kompilacji wywołań w celu wyświetlenia nowych wartości, jeśli jest to wymagane.
new TimeSpan(2000)
nie oznacza to 2000 milisekund, ale 2000 „taktów”, co oznacza 0,2 milisekundy lub jedną 10.000 sekund.