Przechodząc na język C# (z Delphi, które jest pochodną Pascala), chcąc nie chcąc programuje się zgodnie z wyrobionych dotąd stylem i wg starych przyzwyczajeń. Niemniej, staram się weryfikować, czy obrana droga rzeczywiście jest tą, z której należy korzystać na nowym terenie, jakim jest C#. W Delphi często korzystałem z możliwości domyślnych parametrów, tak w konstruktorach, jak i w metodach (procedurach i funkcjach). Ostatnio mój kolega zauważył, że C# do konstrukcji klas nie używa konstruktorów z domyślnymi parametrami (jest to możliwe od wersji C# 4.0), zamiast tego je przeciążając. Zacząłem się zastanawiać czy powinienem trzymać się tej konwencji, aby nie pójść w maliny. Nurtowało mnie przede wszystkim, skąd ta konwencja się wzięła, przecież bardziej czytelne jest stworzenie tylko jednego konstruktora, który może być w szczególności traktowany także jako ten podstawowy, bezparametrowy, a przy okazji obsłużyć także inne swoje warianty:

public Price(Currency aCurrency = default(Currency), Quota aQuota = null)
{
  Currency = aCurrency;
  Quota = aQuota;
}

To zdecydowanie krótsze, prostsze i czytelniejsze niż ciąg następujących konstruktorów:

public Price(Currency aCurrency, Quota aQuota)
{
  Currency = aCurrency;
  Quota = aQuota;
}

public Price() : this(default(Currency), null) { }

public Price(Currency aCurrency) : this(aCurrency, null) { }

public Price(Quota aQuota) : this(default(Currency), aQuota) { }

Używanie przeciążania sprawia, że jeżeli przyjdzie dodać kolejny parametr, wówczas ilość przeciążeń wzrasta – to nie jest ani wygodne, ani łatwe w zarządzaniu. Z drugiej strony dla parametrów domyślnych nie mogę tak wywołać konstruktora, aby pierwszemu parametrowi została nadana wartość domyślna, a drugi określiłbym ja, ponieważ konwencja parametrów domyślnych zakłada, że to te nieokreślone (zgodnie z kolejnością występowania w definicji konstruktora/metody) są wypełniane domyślną wartością. Zatem nie mogę pominąć pierwszego, bo jest on potrzebny, aby wskazać drugi (czyli ten, którego wartość chciałbym ustalić). Chcąc nie chcąc jestem zmuszony do nadania wartości także pierwszemu. Zaraz, zaraz! Czy rzeczywiście? Otóż nie, możliwe jest używanie parametrów nazwanych (również poczynając od wersji 4.0), tj. umieszczony w pierwszym przykładzie kodu konstruktor z parametrami domyślnymi można wywołać następująco:

Price p = new Price(aQuota: new Quota());

Tym sposobem parametrowi aCurrency zostanie nadana wartość domyślna. Na marginesie warto dodać, że podobną konwencję stosuje język T-SQL. W Delphi (przynajmniej do wersji 2010) nie było takiej możliwości.

Dlaczego więc w .NET stosowane jest przeciążanie? Uważny czytelnik zapewne domyślił się już odpowiedzi. Jak wspomniałem w tym wpisie dwukrotnie, parametry domyślne dostępne są dopiero w wersji C# 4.0, a wszystkie dotychczasowe biblioteki powstały w wersjach wcześniejszych. Zatem konwencja przeciążania wynika z przyczyn historycznych, a nie jakiejś sekretnej reguły.

Jeśli zatem pisany kod będzie kompilowany w wersji C# 4.0, można swobodnie korzystać z udogodnień tejże wersji. Jeśli jednak ma być uruchamiany także w starszych wersjach – nie ma wyjścia – trzeba przeciążać (tak jest np. dla oprogramowania na urządzenia wykorzystujące Windows Mobile 5.0/6.0).

Rozważając konstruktory w kontekście parametrów domyślnych warto jeszcze zauważyć, że alternatywnym do nich rozwiązaniem jest skorzystanie z inicjalizatorów obiektów, tj. następującej konwencji:

Price p = new Price() { Qutoa = new Quota(), Curency = Currency.PLN}

niemniej nie zawsze jest to możliwe, albowiem publiczne właściwości (a tych użyto w nawiasach klamrowych powyżej) nie zawsze bywają dostępne do zapisu (często – ze względu na potrzeby hermetyzacji – nie są). I pozostaje wówczas kombinacja typu:

Price p = new Price(Currency.PLN) { Qutoa = new Quota()}

lub:

Price p = new Price() { Qutoa = new Quota()}

jeżeli istnieje odpowiednie przeciążenie konstruktora, które podstawi pod Currency stosowną, domyślna wartość. Inicjalizatory obiektu dostępne są od wersji 3.0 języka, zatem poczynając od tejże wersji, można korzystać z takiej alternatywy.

Czy to wszystko co można napisać w tym temacie? Jeszcze nie ;). Należy jeszcze wspomnieć, że parametry domyślne są de facto realizowane przez kompilator. Jeżeli zatem używamy zewnętrznej biblioteki i otrzymamy jej nową wersję, to konieczna będzie rekompilacja kodu, który jej używa, jeżeli nowe wartości parametrów domyślnych mają zostać uwzględnione (gdyby tak się nie stało, wówczas to, co zostało skompilowane z poprzednimi domyślnymi parametrami, pozostanie w mocy). Bywa też tak, że niektóre fragmenty .NET wręcz wymagają przeciążania konstruktorów, np. WPF wymaga dla własnych kontrolek, wywoływanych z XAML-a domyślnego, bezparametrowego konstruktora (nie może to być konstruktor z domyślnymi parametrami, ale explicite bezparametrowy konstruktor).

Podsumowując: nie wszystko w świecie .NET rozumie domyślne parametry (do niedawna nawet C# ich nie rozumiał, choć Visual Basic miał je od dawna), niemniej – z tymi nielicznymi wyjątkami – nic nie stoi na przeszkodzie, aby korzystać z ich dobrodziejstwa. To jest po prostu wygodne!