Metody

TomaszSmykowski

Czym są metody

Metody są głównymi składowymi klas, można je również definiować w strukturach. Metody definiują zachowanie klas(struktur), umożliwiając im wykonywanie konkretnych zadań, dlatego też metod nie można zdefiniować poza ciałem klasy lub struktury.

Definiowanie metod

Metody definiowane są przez swoją sygnaturę oraz ciało metody.
Sygnatura metody składa się z:

  • nazwy metody
  • listy typów i nazw parametrów metody
    Np.
private void MetodaInicjujacaObiekt()   /*typ zwracany przez metodę: void(metoda nie zwraca żadnej wartości), 
                                          nazwa: MetodaInicjujacaObiekt, 
                                          w nawiasie nie ma paramertów metody*/
public int MetodaZwracajacaSume(int liczbaA, int liczbaB) /*typ zwracany przez metodę: int, 
                                                              nazwa metody: MetodaZwracajacaSume, 
                                                              metoda pobiera dwa parametry: liczbaA i liczbaB oba parametry typu int*/

Sygnatura metody w obrębie klasy w której jest definiowana musi być unikatowa, nie można zdefiniować dwóch metod o identycznej sygnaturze.

Ciałem metody nazywa się kod który będzie wykonywany po wywołaniu metody a który jest definiowany w nawiasach sześciennych bezpośrednio pod definicją sygnatury metody, np.

private void MetodaInicjujacaObiekt()    
{
    //cały zestaw metod które są tu
    //wykonywane po wywołaniu metody 
    //MetodaInicjującaObiekt() 
}

public int MetodaZwracajacaSume(int liczbaA, int liczbaB) 
{
    return liczbaA + liczbaB; //zwracanie wyniku dodawania
}

Kod w ciele metody ma tylko jeden warunek jeżeli metoda zwraca jakąś wartość (tzn. wtedy kiedy w jej sygnaturze typ zwracany jest różny od void) w ciele tej metody musi być instrukcja return gwarantująca zwrócenie wartości zdefiniowanego typu, przez metodę.

Można też definiować metody z ciałem w postaci wyrażenia, przykład niżej:

public class Sumator
    {
        public int Dodaj(int liczbaA, int liczbaB) =>
            liczbaA + liczbaB;

        static public int DodajStatycznie(int liczbaA, int liczbaB) =>
            liczbaA + liczbaB;
    }

Wywoływanie metody zawsze odbywa się za pomocą operatora kropki.
W przypadku metod wywoływanych z instancji konkretnych obiektów należy najpierw podać nazwę utworzonego obiektu /kropka/ i podać nazwę wywoływanej metody, jeżeli metoda przyjmuje jakie argumenty należy je podać w nawiasach - przykład niżej.
W przypadku metod statycznych /operator static opisany jest niżej/ należy podać nazwę klasy /Typu/ w której jest zdefiniowana metoda statyczna /kropka/ i analogicznie jak wyżej podać nazwę wywoływanej metody.

        static void Main(string[] args)
        {
            Sumator sumatoreczek = new Sumator();

            Console.WriteLine($"Wynik dodawania metoda wywolana z obiektu: { sumatoreczek.Dodaj(1, 3) } ");
            Console.WriteLine($"Wynik dodawania metoda wywolana statycznie: {Sumator.DodajStatycznie(4, 5)}");
            Console.ReadLine();
        }

Poza sygnaturą i ciałem, metoda charakteryzuje się jeszcze listą modyfikatorów, które decydują m.in. o dostępności metody i/lub zachowaniu polimorficznym metody.

Modyfikatory metod

Poza standardowymi modyfikatorami dostępu, metody obsługują 7 dodatkowych modyfikatorów:

  • virtual
  • override
  • new
  • sealed
  • abstract
  • **static **
  • extern
    Pierwsze 5 z wymienionych powyżej mają związek z dziedziczeniem i polimorfizmem.

Modyfikator static, oznacza że metoda z takim modyfikatorem może być wywoływana bez konieczności tworzenia obiektu w którym została zdefiniowana, np.

    public class Kalkulator
    {
        private void NazwaMetodyBezparametrowej()    
        {
            //cały zestaw metod które są tu
            //wykonywane po wywołaniu metody 
            //NazwaMetodyBezparametrowej()
        }

        public static int MetodaZwracajacaSume(int liczbaA, int liczbaB) 
        {
            return liczbaA + liczbaB; //zwracanie wyniku dodawania
        }

        public int MetodaZwracajacaRoznice(int liczbaWieksza, int liczbaMniejsza)
        {
            return liczbaWieksza - liczbaMniejsza;
        }
    }

W przykładzie powyżej nie trzeba tworzyć obiektu typu Kalkulator aby policzyć sumę dwóch liczb, dzięki modyfikatorowi static możliwe jest wywołanie jak niżej:

int wynik = Kalkulator.MetodaZwracajacaSume(3, 5);

Ale już aby policzyć róznicę należy utworzyć obiekt Kalkulator i dopiero z niego wywoływać metodę obliczającą różnicę, np.

Kalkulator calc = new Kalkulator();
int wynik = calc.MetodaZwracajacaRoznice(7, 5);

Modyfikatory virtual,** override** i new. Używane są w odniesieniu do metod przy dziedziczeniu. Klasycznie przy dziedziczeniu z klasy bazowej nie ma możliwości aby w podtypie do typu bazowego zmienić definicję odziedziczonej metody publicznej. Chyba, że w typie bazowym oznaczymy taką metodę modyfikatorem virtual. Oznacza to dla kompilatora, że ta metoda może być zmieniana w podtypach które odziedziczą z tego typu bazowego. A modyfikator override służy właśnie w podtypach, do przedefiniowywania metod oznaczonych w bazowym typie jako virtual. Przykład, poniżej:

    public class Zwierze
    {
        public virtual string DajGlos() { return "Zwierze daje GŁOOOS!!"; }        
    }

    public class Kotowate : Zwierze
    {
        public override string DajGlos() { return "Kotowate GRRRRR!!!"; }
    }

    public class DomowyKotek : Kotowate
    {
        public override string DajGlos() { return "Miau, miau...."; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Zwierze zwierz1 = new Kotowate();
            Zwierze zwierz2 = new DomowyKotek();
            string uslyszanyGlosZwierzaka;
            uslyszanyGlosZwierzaka = zwierz1.DajGlos();    // "Kotowate GRRRRR!!!"
            uslyszanyGlosZwierzaka = zwierz2.DajGlos();    // "Miau, miau...."
        }        
    }

Modyfikator override powoduje nadpisanie w podtypie Kotowate metody odziedziczonej z typu bazowego Zwierze metody oznaczonej modyfikatorem virtual. DomowyKotek dziedziczy z Kotowate i nadpisuje metodę Kotowatych swoją własną, gdyby DomowyKotek nie nadpisywał metody** DajGlos()**, wyniki byłyby jak w przykładzie poniżej:

    public class Zwierze
    {
        public virtual string DajGlos() { return "Zwierze daje GŁOOOS!!"; }        
    }

    public class Kotowate : Zwierze
    {
        public override string DajGlos() { return "Kotowate GRRRRR!!!"; }
    }

    public class DomowyKotek : Kotowate
    {
    }

    class Program
    {
        static void Main(string[] args)
        {
            Zwierze zwierz1 = new Kotowate();
            Zwierze zwierz2 = new DomowyKotek();
            string uslyszanyGlosZwierzaka;
            uslyszanyGlosZwierzaka = zwierz1.DajGlos();    // "Kotowate GRRRRR!!!"
            uslyszanyGlosZwierzaka = zwierz2.DajGlos();    // "Kotowate GRRRRR!!!"                  
        }        
    }

Ponadto jeżeli dla przykładów powyżej zmienimy typy z których będziemy wywoływać metody** DajGlos()**, na konkretne typu Kotowate i DomowyKotek – uzyskany efekt będzie taki sam jak w przykładach powyżej:

    Kotowate zwierz1 = new Kotowate();
    DomowyKotek zwierz2 = new DomowyKotek();
    string uslyszanyGlosZwierzaka;
    uslyszanyGlosZwierzaka = zwierz1.DajGlos();    
    uslyszanyGlosZwierzaka = zwierz2.DajGlos(); 

Czyli override powoduje trwałą zmianę(trwałe nadpisanie) metody z typu bazowego i niezależnie od tego jak wywołujemy tą nadpisaną metodę:

Czy tak:

    Kotowate zwierz1 = new Kotowate();
    string uslyszanyGlosZwierzaka;
    uslyszanyGlosZwierzaka = zwierz1.DajGlos();  

Czy tak:

    Zwierze zwierz1 = new Kotowate();
    string uslyszanyGlosZwierzaka;
    uslyszanyGlosZwierzaka = zwierz1.DajGlos();   

obiekt zwierz1 wywoła metodę nadpisaną (metodę oznaczoną słowkiem override z typu Kotowate, ponieważ obiekt był tworzony przez new Kotowate() ).

Modyfikator new natomiast powoduje zdefiniowanie w podtypie „obok” metody bazowej virtual, nowej metody która będzie wywoływana tylko z podtypu. Rzutowanie podtypu na typ bazowy i późniejsze wywołanie tej metody, uruchomi metodę z typu bazowego, przykład:

    public class Zwierze
    {
        public virtual string DajGlos() { return "Zwierze daje GŁOOOS!!"; }        
    }

    public class Kotowate : Zwierze
    {
        new public string DajGlos() { return "Kotowate GRRRRR!!!"; }
    }

    public class DomowyKotek : Kotowate
    {
        new public string DajGlos() { return "Miau, miau...."; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Kotowate zwierz1 = new Kotowate();
            DomowyKotek zwierz2 = new DomowyKotek();
            string uslyszanyGlosZwierzaka;
            uslyszanyGlosZwierzaka = zwierz1.DajGlos();    // "Kotowate GRRRRR!!!"
            uslyszanyGlosZwierzaka = zwierz2.DajGlos();    // "Miau, miau...."
        }        
    }

W przykładzie powyżej w podtypach Kotowate i DomowyKotek utworzyliśmy nowe implementacje metod string DajGlos() dla tych podtypów. Widać, że tworząc te podtypy i wywołując z nich metody DajGlos() otrzymujemy wyniki działania tych nowych metod, ale wystarczy, że w wywołaniach utworzone podtypy przypiszemy do obiektów typu Zwierz i wywoływana będzie metoda bazowa oznaczona jako virtual z klasy bazowej dla tych typów, przykład niżej:

    public class Zwierze
    {
        public virtual string DajGlos() { return "Zwierze daje GŁOOOS!!"; }        
    }

    public class Kotowate : Zwierze
    {
        new public string DajGlos() { return "Kotowate GRRRRR!!!"; }
    }

    public class DomowyKotek : Kotowate
    {
        new public string DajGlos() { return "Miau, miau...."; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Zwierze zwierz1 = new Kotowate();
            Zwierze zwierz2 = new DomowyKotek();
            string uslyszanyGlosZwierzaka;
            uslyszanyGlosZwierzaka = zwierz1.DajGlos();    // "Zwierze daje GŁOOOS!!"
            uslyszanyGlosZwierzaka = zwierz2.DajGlos();    // "Zwierze daje GŁOOOS!!"
        }        
    }

Dzieje się tak dlatego bo słowko new nie nadpisuje metody oznaczonej jako virutal, ale tworzy nową metodę która działa tylko dla tego konkretnego podtypu.

Modyfikatory sealed i abstract. W odniesieniu do tego co zostało przedstawione powyżej to metody oznaczone modyfikatorem sealed nie mogą być nadpisywane, czyli dziedzicząc po obiekcie zawierającym metodę oznaczoną jako sealed, w podtypie nie można nadpisać tej metody (override). Modyfikator sealed można używać tylko do metod oznaczonych jako override, nie można go używać do metod oznaczonych modyfikatorem virtual.

Modyfikator abstract, nakłada zobowiązanie na klasę potomną aby zaimplementowała tą metodę. Klasa bazowa zawierająca metodę oznaczoną jako abstract, również musi być oznaczona jako abstract. Metoda oznaczona jako abstract nie posiada implementacji, posiada tylko sygnaturę, obowiązek dodania implementacji metody spoczywa na podtypach. (analogicznie jak w interfejsach, również w interfejsie definiujemy tylko sygnatury metod a dopiero w klasach które używają danego interfejsu są implementacje tych metod).

Modyfikator extern określa, że dana metoda jest implementowana zewnętrznie. Ogólnie, modyfikator extern stosuje się łącznie z atrybutem DLLImport wskazującym bibliotekę DLL, która zawiera odpowiednią implementację.* (źródło: „C# i .NET” Stephen C. Perry)

Przekazywanie parametrów do metod

Domyślnie parametry do metod są przekazywane przez wartość, tzn. w momencie wywołania metody z parametrami, do tej metody są przekazywane kopie tych parametrów, a nie same parametry, wszelkie zmiany wewnątrz metody na tych wartościach nie będą miały wpływu na oryginalne dane, dotyczy to parametrów typu wartościowego. Jeżeli jednak jako parametr metody przekazywany jest do niej typ referencyjny, to do metody trafia referencja na ten obiekt i wszelkie zmiany na obiekcie wykonywane wewnątrz metody będą odzwierciedlone w oryginalnym obiekcie, przykład:

    public class Zwierze
    {        
    }

    public class Biedronka : Zwierze
    {
        private static int iloscKropekNaSkrzydelkach;

        public static void UstawIloscKropekNaSkrzydelkach(int ilosc)
        {
            iloscKropekNaSkrzydelkach = ilosc;
            ilosc = 0;                            // tylko na potrzeby przykladu
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int liczba = 15;

            Console.WriteLine(liczba);            // wyświetli: 15
            Biedronka.UstawIloscKropekNaSkrzydelkach(liczba);
            Console.WriteLine(liczba);            // wyświetli: 15

            Console.ReadLine();
        }        
    }
Wynik z konsoli:
15
15

W powyższym przykładzie widać że pomimo tego że do metody ustawiającej ilość kropek na skrzydełkach biedronki przekazaliśmy parametr liczba i pomimo tego że ten parametr wewnątrz tej metody został wyzerowany, oryginalny parametr** liczba** również po wykonaniu metody miał niezmienioną wartość 15. Ale jeżeli zamiast typu wartościowego przekazujemy do metody typ referencyjny, sprawa zmienia się diametralnie, przykład:

    public class Zegar
    {
        public int Godzina = 12;
        public int Minuta = 45;

        public string ZwrocUstawionaGodzine()
        {
            return Godzina.ToString()+":"+Minuta.ToString();
        }
    }

    public class Zegarmistrz
    {        
        public static void UstawZadanaGodzineNaZegarku(Zegar zegarek, int zadanaGodzina, int zadanaMinuta)
        {
            zegarek.Godzina = zadanaGodzina;
            zegarek.Minuta = zadanaMinuta;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Zegar budzik = new Zegar();

            Console.WriteLine(budzik.ZwrocUstawionaGodzine());           // wyswietli 12:45
            Zegarmistrz.UstawZadanaGodzineNaZegarku(budzik, 17, 15);
            Console.WriteLine(budzik.ZwrocUstawionaGodzine());           // wyswietli 17:15

            Console.ReadLine();
        }        
    }
Wynik z konsoli:
12:45
17:15

Widać że u Zegarmistrza w jego metodzie statycznej ustawiającej zadana godzine na zegarku jako parametr przekazywany jest obiekt typu Zegar, jest to typ referencyjny czyli wszystko co zostało na tym obiekcie wykonane wewnątrz metody, zostało również odzwierciedlone w oryginalnym obiekcie zegarku.

Modyfikatory out i ref parametrów metod

W C# można typy wartościowe przekazywać do metod z podobnym skutkiem co typy referencyjne ale należy te parametry w sygnaturze metody oznaczyć odpowiednim modyfikatorem: out lub ref.

Modyfikator out, używa się aby oznaczyć parametr metody który zostanie zainicjowany dopiero w ciele tej metody. Można to rozumieć tak że pomimo tego że metoda nie zwraca żadnej wartości (void), to tak oznaczony parametr staje się jej wartością którą ona zwraca, przykład:

public class InicjalizatorTablic
    {
        public static void InicjujTabliceZliczenDlaTypu_Int(out int[] tablica, int rozmiarTablicy, int wartoscDomyslna)
        {
            tablica = new int[rozmiarTablicy];

            for (int i = 0; i < tablica.Length; i++)
            {
                tablica[i] = wartoscDomyslna;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int[] jakasTablica;

            InicjalizatorTablic.InicjujTabliceZliczenDlaTypu_Int(out jakasTablica, 10, 99);
            WypiszZawartoscTablicy(jakasTablica);

            Console.ReadLine();
        }        

        static void WypiszZawartoscTablicy(int[] tablica)
        {
            if (tablica!=null)
            {
                for (int i = 0; i < tablica.Length; i++)
                {
                    Console.WriteLine("Wartość elementu "+i+" tablicy, wynosi: "+tablica[i].ToString());
                }
            }
        }
    }
Wynik wyświetlany w konsoli:
Wartość elementu 0 tablicy, wynosi: 99
Wartość elementu 1 tablicy, wynosi: 99
Wartość elementu 2 tablicy, wynosi: 99
Wartość elementu 3 tablicy, wynosi: 99
Wartość elementu 4 tablicy, wynosi: 99
Wartość elementu 5 tablicy, wynosi: 99
Wartość elementu 6 tablicy, wynosi: 99
Wartość elementu 7 tablicy, wynosi: 99
Wartość elementu 8 tablicy, wynosi: 99
Wartość elementu 9 tablicy, wynosi: 99

Widać dokładnie że w metodzie Main, tablica int[] jakasTablica została zdefiniowana ale nie zainicjowana, inicjalizacja odbyła się w metodzie InicjalizatorTablic.InicjujTabliceZliczenDlaTypu_Int
Warto zauważyć że zarówno w definicji tej metody jak i wywołaniu parametr jest oznaczony modyfikatorem out.

Modyfikator ref, używa się aby oznaczony tak parametr mógł zostać zmieniony w ciele metody ale ze skutkiem zmian widocznym w oryginalnym parametrze, przykład:

    public class InicjalizatorTablic
    {
        public static void InicjujTabliceZliczenDlaTypu_Int(out int[] tablica, int rozmiarTablicy, int wartoscDomyslna)
        {
            tablica = new int[rozmiarTablicy];

            for (int i = 0; i < tablica.Length; i++)
            {
                tablica[i] = wartoscDomyslna;
            }
        }

        public static void ZmienWartoscDomyslnaTablicyInt(ref int[] tablica, int nowaWartoscDomyslna)
        {
            if (tablica!=null)
            {
                for (int i = 0; i < tablica.Length; i++)
                {
                    tablica[i] = nowaWartoscDomyslna;
                }
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int[] jakasTablica;

            InicjalizatorTablic.InicjujTabliceZliczenDlaTypu_Int(out jakasTablica, 10, 99);
            WypiszZawartoscTablicy(jakasTablica);

            InicjalizatorTablic.ZmienWartoscDomyslnaTablicyInt(ref jakasTablica, 3);
            Console.WriteLine("\r\nWartości po aktualizacji wartosci domyślnej:");
            WypiszZawartoscTablicy(jakasTablica);

            Console.ReadLine();
        }        

        static void WypiszZawartoscTablicy(int[] tablica)
        {
            if (tablica!=null)
            {
                for (int i = 0; i < tablica.Length; i++)
                {
                    Console.WriteLine("Wartość elementu "+i+" tablicy, wynosi: "+tablica[i].ToString());
                }
            }
        }
    }
Wynik wyświetlany w konsoli:
Wartość elementu 0 tablicy, wynosi: 99
Wartość elementu 1 tablicy, wynosi: 99
Wartość elementu 2 tablicy, wynosi: 99
Wartość elementu 3 tablicy, wynosi: 99
Wartość elementu 4 tablicy, wynosi: 99
Wartość elementu 5 tablicy, wynosi: 99
Wartość elementu 6 tablicy, wynosi: 99
Wartość elementu 7 tablicy, wynosi: 99
Wartość elementu 8 tablicy, wynosi: 99
Wartość elementu 9 tablicy, wynosi: 99

Wartości po aktualizacji wartosci domyślnej:
Wartość elementu 0 tablicy, wynosi: 3
Wartość elementu 1 tablicy, wynosi: 3
Wartość elementu 2 tablicy, wynosi: 3
Wartość elementu 3 tablicy, wynosi: 3
Wartość elementu 4 tablicy, wynosi: 3
Wartość elementu 5 tablicy, wynosi: 3
Wartość elementu 6 tablicy, wynosi: 3
Wartość elementu 7 tablicy, wynosi: 3
Wartość elementu 8 tablicy, wynosi: 3
Wartość elementu 9 tablicy, wynosi: 3

Załatwione ;) metody nic nie zwracają a jednak modyfikują wartości zdefiniowane poza swoimi ciałami. W przypadku modyfikatora **ref **również musi być podany przy parametrze zarówno podczas definiowania metody jak i jej wywoływania.

Przeciążanie metod

Pod tym ciężko i skomplikowanie brzmiącym terminem kryje się banalna sprawa. Jak na samym początku zostało omówione metoda składa się między innymi z sygnatury, która natomiast składa się z typu jaki zwraca dana metoda, z nazwy i z listy parametrów i ich typów. I teraz, sygnatura metody MUSI być unikalna w skali klasy w której metoda jest zdefiniowana. Ale, że sygnatura metody składa się z trzech składowych okazuje się że w jednej klasie mogą być metody o identycznej nazwie .

Oczywiście należy zaraz dodać, że ok. mogą być takie same nazwy metod w jednej klasie ale każda z nich musi różnić się ilością lub typami parametrów, tak aby sygnatura każdej z nich w obrębie klasy była unikatowa.

Czyli w skrócie, przeciążone metody to takie które:

  • mają taki sam typ zwracany
  • mają identyczną nazwę
  • różnią się tylko ilością lub typami przekazywanych parametrów
    ;)

Aby nie wymyślać koła na nowo posilę się przykładem listy przeciążonych metod Show() klasy **MessageBox **ze strony MSDN.

http://msdn.microsoft.com/pl-pl/library/system.windows.forms.messagebox.show%28v=vs.110%29.aspx

Widać że każda metoda ma nazwę Show, każda zwraca DialogResult ale wszystkie one różnią się parametrami jakie przyjmują.

Nazewnictwo metod

Chyba najczęściej stosowanymi regułami przy nadawaniu nazw metodom są:

  • Nazwa metody zapisana jest wielką literą.
  • Jeżeli nazwa składa się wielu wyrazów, pisze się je razem ale pierwsze litery wszystkich wyrazów pisze się wielką literą.

3 komentarzy

Typ zwracany przez metodę nie wchodzi w skład sygnatury metody. Sygnatura to jedynie nazwa oraz parametry.

Poprawione, dzięki :)

  1. int liczba_a - a może by tak zgodnie z konwencją: int liczbaA?
  2. "z wielkiej litery" => "wielką literą"
  3. "w ciele tej metody musi być instrukcja return ," - niepotrzebny przecinek przed "return"
  4. "w dziedziczeniu z klasy bazowej nie ma opcji aby w podtypie" => "przy dziedziczeniu z klasy bazowej nie ma możliwości"
  5. Ja bym dodał jeszcze informację o tym, że jeśli klasa zawiera metodę abstrakcyjną, to też musi być abstrakcyjna.
  6. Brakuje jakiegoś wyjaśnienia (odnośnika do innego Twojego artykułu może) o różnicach między typami wartościowymi a referencyjnymi.

A tak poza tymi szczegółami to kawał dobrej roboty!