Kilka ciekawostek z przeładowywania metod, część II

Po krótkiej przerwie, wracam do blogowania. Ostatnio, pokazałem jak C# traktuje przeładowywanie metod. Nie zawsze jest to proste i czasami naprawdę może spowodować błędy w interpretacji. Jeszcze raz chcę powtórzyć, że celem wpisów NIE jest zachęcenie czytelników do pisania skomplikowanych przeładowań. Wręcz odwrotnie – wiedząc jak trudno odgadnąć wynik należy po prostu unikać przedstawionych konstrukcji.

Dla przypomnienia, ostatnio zatrzymaliśmy się na override:

internal class Program
{
   private static void Main(string[] args)
   {
       Child child = new Child();
       child.Display((int) 5);
   }
}

internal class Parent
{
   public virtual void Display(int arg)
   {
       Console.WriteLine("int");
   }
}

internal class Child : Parent
{
   public override void Display(int arg)
   {
       Console.WriteLine("override int");
   }

   public void Display(double arg)
   {
       Console.WriteLine("double");
   }
}

Jako zostało to już wyjaśnione, C# w pierwszej kolejności ignoruje metody override i przejdzie do Display(double args) – co może wydawać się dziwne.

Zmodyfikujmy nieco przykład:

internal class Parent
{
   public void Display(int arg)
   {
       Console.WriteLine("int");
   }
}

internal class Child : Parent
{
   public new void Display(int arg)
   {
       Console.WriteLine("method hidding int");
   }

   public void Display(double arg)
   {
       Console.WriteLine("double");
   }
}

Używamy teraz method-hiding a nie polimorfizmu. Jaki to ma pływ na przeładowywanie metod? Taki, że zostanie wywołana metoda Display(int) – ponieważ jest ona najbardziej podobna do wywołania oraz nie zawiera override w sygnaturze. Przykład pokazuje, jaka jest różnica w interpretacji przeładowań dla wirtualnych i przykrytych metod.

Override nie jest całkowicie ignorowany. Jeśli nie ma nigdzie nadającej się do użycia sygnatury, kompilator oczywiście wróci i wywoła metodę override. Podobnie w przypadku, gdy metody bez override, nie mają wystarczająco dobrej (tzn. dopasowanej) sygnatury:

internal class Program
{
   private static void Main(string[] args)
   {
       Child child = new Child();
       child.Display((int) 5);
   }
}

internal class Parent
{
   public virtual void Display(int arg)
   {
       Console.WriteLine("int");
   }

   public void Display(long arg)
   {
       Console.WriteLine("arg");
   }
}

internal class Child : Parent
{
   public override void Display(int arg)
   {
       Console.WriteLine("override int");
   }
}

Zostanie tutaj wywołana metoda Display(int arg) – ta przeciążona. Jeśli nie istnieje lepsza, nieprzeciążona sygnatura to ta z override zostanie użyta.

Kolejny przykład:

internal class Program
{
   private static void Main(string[] args)
   {
       Child child = new Child();
       child.Display((Int16) 5);
   }
}

internal class Parent
{
   public virtual void Display(long arg)
   {
       Console.WriteLine("virtual int");
   }

   public void Display(int arg)
   {
       Console.WriteLine("int");
   }
}

internal class Child : Parent
{
   public override void Display(long arg)
   {
       Console.WriteLine("override int");
   }
}

Tutaj zostanie wywołana Display(int arg), ponieważ jest to najlepsza sygnatura i nie ma potrzeby “cofania się” do override.

Mieszanie przeładowania metod z dziedziczeniem, jak widać, nie jest najlepszym pomysłem.

Na zakończenie pewna uwaga o domyślnych wartościach:

private static void Main(string[] args)
{
  Display(1);
}

private static void Display(int a, string b=null)
{
  
}
private static void Display(int a)
{

}

Zostanie wywołana metoda Display(int a), ponieważ nie ma ona domyślnych wartości dla parametrów i spełnia wymagania. Lepsze dopasowanie jest takie, które nie potrzebuje korzystać z dobrodziejstw domyślnych wartości. Inna sytuacja to:

internal class Program
{
   private static void Main(string[] args)
   {
       Display(1);
   }

   private static void Display(int a, string b=null)
   {
       
   }
   private static void Display(int a,int b=5)
   {

   }
}

Kod nie skompiluje się, ponieważ nie wiadomo, która metoda powinna zostać wywołana. Powyższe dwa przykłady wydają się sensowne i logiczne, ale mimo wszystko mogą spowodować poważne problemy w wersjonowaniu kodu.

Leave a Reply

Your email address will not be published.