Gdy w kilku klasach znajdujemy cechy wspólne, tworzymy klasę nadrzędną, po której będą one dziedziczone.

Dzięki takiemu zabiegowi pozbywamy się powtarzania identycznego kodu.
Ale co w przypadku gdy klasa, która stworzyliśmy, aby po niej dziedziczyć jest zbyt ogólna. Przez co nie nadaje się by tworzyć jej obiektów?

Ten problem rozwiązują klasy abstrakcyjne.

Aby stworzyć klasę abstrakcyjną używamy słowa kluczowego abstract przed słowem class. Zobaczmy przykład:
Napiszmy najpierw dwie proste klasy:


   public  class Pracownik {
     
        private string imie="";
        private string nazwisko="";
        private int wiek=1;
        private string stanowisko = "";
        private decimal pensja = 0;
     
        public string Imie {
            get {
                return imie;
            }

            set {
                imie = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public string Nazwisko{
            get {
                return nazwisko ;
            }

            set
            {
                nazwisko = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public int Wiek{
            get {
                return wiek;
            }

            set {
                wiek = (int)SprawdzLiczbe(value);
            }
        }

        public string Stanowisko
        {
            get
            {
                return stanowsiko;
            }

            set
            {
                stanowisko = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public decimal Pensja
        {
            get
            {
                return pensja;
            }

            set
            {
                pensja = SprawdzLiczbe(value);
            }
        }

        private decimal SprawdzLiczbe(decimal liczba) {
            return liczba < 0 ? liczba * (-1) : liczba;
        }

        private string ZmienPierwszaLitereNaDuza(string s) {
            s = s.ToLower();
            if (string.IsNullOrEmpty(s)) { 
                return string.Empty;
            }     
            return char.ToUpper(s[0]) + s.Substring(1);

        }

         public void WypiszDaneOsobowe(){
            Console.WriteLine("Imie: {0} Nazwisko {1} , Wiek {2}", imie, nazwisko, wiek);
           
        }

 	public void WypiszDaneDodatkowe(){
            Console.WriteLine("pracownik znajduje sie na stanowisku: {0} zarabia {1}", stanowisko,pensja);
           
        }   
    }

 public class Student
    {

        private string imie = "";
        private string nazwisko = "";
        private int wiek = 1;
        private int semestr = 0;

        private List<string> przedmioty = new List<string>();

        
        public string Imie
        {
            get
            {
                return imie;
            }

            set
            {
                imie = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public string Nazwisko
        {
            get
            {
                return nazwisko;
            }

            set
            {
                nazwisko = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public int Wiek
        {
            get
            {
                return wiek;
            }

            set
            {
                wiek = (int)SprawdzLiczbe(value);
            }
        }

        public int Semestr
        {
            get
            {
                return semestr;
            }

            set
            {
                semestr = (int)SprawdzLiczbe(value);
            }
        }

        private decimal SprawdzLiczbe(decimal liczba)
        {
            return liczba < 0 ? liczba * (-1) : liczba;
        }

        private string ZmienPierwszaLitereNaDuza(string s)
        {
            s = s.ToLower();
            if (string.IsNullOrEmpty(s))
            {
                return string.Empty;
            }
            return char.ToUpper(s[0]) + s.Substring(1);

        }

        public void DodajPrzedmiot(string przedmiot) {
            przedmioty.Add(przedmiot);
        }

        public void UsunPrzedmiot(string przedmiot) {
            przedmioty.Remove(przedmiot);
        }
        public void WypiszDaneOsobowe()
        {
            Console.WriteLine("Imie: {0} Nazwisko {1} , Wiek {2}", imie, nazwisko, wiek);
           
        }

        public void WypiszDaneDodatkowe() {

            Console.WriteLine("Student znajduje się na semestrze:{0}",semestr);
            Console.WriteLine("Student uczestniczy w przedmiotach: ");

            foreach (var przedmiot in przedmioty)
            {
                Console.WriteLine(przedmiot);
            }
        }  

    }

Jak widzimy klasy te maja dużo wspólnych elementów “wyciągnijmy je” do nowej klasy abstrakcyjnej o nazwie Osoba:


    public abstract class Osoba {
        private string imie = "";
        private string nazwisko = "";
        private int wiek = 1;
        public string Imie
        {
            get
            {
                return imie;
            }

            set
            {
                imie = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public string Nazwisko
        {
            get
            {
                return nazwisko;
            }

            set
            {
                nazwisko = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public int Wiek
        {
            get
            {
                return wiek;
            }

            set
            {
                wiek = (int)SprawdzLiczbe(value);
            }
        }

        protected decimal SprawdzLiczbe(decimal liczba)
        {
            return liczba <0 ? liczba * (-1) : liczba;
        }
        protected string ZmienPierwszaLitereNaDuza(string s)
        {
            s = s.ToLower();
            if (string.IsNullOrEmpty(s))
            {
                return string.Empty;
            }
            return char.ToUpper(s[0]) + s.Substring(1);
        }

        public void WypiszDaneOsobowe()
        {
            Console.WriteLine("Imie: {0} Nazwisko {1} , Wiek {2}", imie, nazwisko, wiek);

        }

    }

Klasa Osoba jest zbyt ogólna nie chcemy tworzyć jej obiektów dlatego do jej zdefiniowania użyłem słowa kluczowego abstract.
Spróbujmy stworzyć jej obiekt:


class Program{
        static void Main(){
            Osoba test = new Osoba();            
        }
}

abstract1

Rys. Błąd zgłoszony przez kompilator

Jak widzimy nie da się tego zrobić.

Zobaczmy kod po zmianach:

  public abstract class Osoba {
        private string imie = "";
        private string nazwisko = "";
        private int wiek = 1;
        public string Imie
        {
            get
            {
                return imie;
            }

            set
            {
                imie = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public string Nazwisko
        {
            get
            {
                return nazwisko;
            }

            set
            {
                nazwisko = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public int Wiek
        {
            get
            {
                return wiek;
            }

            set
            {
                wiek = (int)SprawdzLiczbe(value);
            }
        }

        protected decimal SprawdzLiczbe(decimal liczba)
        {
            return liczba < 0 ? liczba * (-1) : liczba;
        }
        protected string ZmienPierwszaLitereNaDuza(string s)
        {
            s = s.ToLower();
            if (string.IsNullOrEmpty(s))
            {
                return string.Empty;
            }
            return char.ToUpper(s[0]) + s.Substring(1);
        }

        public void WypiszDaneOsobowe()
        {
            Console.WriteLine("Imie: {0} Nazwisko {1} , Wiek {2}", imie, nazwisko, wiek);

        }

    }

    public  class Pracownik: Osoba {    
   
        private string stanowisko = "";
        private decimal pensja = 0;    
       
        public string Stanowisko
        {
            get
            {
                return stanowisko;
            }

            set
            {
                stanowisko = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public decimal Pensja
        {
            get
            {
                return pensja;
            }

            set
            {
                pensja = SprawdzLiczbe(value);
            }
        }
       
        public void WypiszDaneDodatkowe()
        {
            Console.WriteLine("pracownik znajduje sie na stanowisku: {0} zarabia {1}", stanowisko, pensja);
        }

    }

    public class Student : Osoba {
        private int semestr = 0;

        private List<string>rzedmioty = new List<string>(); 

        public void DodajPrzedmiot(string przedmiot) {
            przedmioty.Add(przedmiot);
        }

        public void UsunPrzedmiot(string przedmiot) {
            przedmioty.Remove(przedmiot);
        } 

        public void WypiszDaneDodatkowe() {

            Console.WriteLine("Student znajduje się na semestrze:{0}",semestr);
            Console.WriteLine("Student uczestniczy w przedmiotach: ");

            foreach (var przedmiot in przedmioty)
            {
                Console.WriteLine(przedmiot);
            }
        }

    }

Obie klasy posiadają metodę public void WypiszDaneDodatkowe() różnią się one jednak między sobą.
Dlatego nie możemy umieścić tej metody w klasie bazowej.
Chcielibyśmy jednak aby wszystkie klasy, które dziedziczą po klasie Osoba posiadały własną wersję tej metody.
Z pomocą przychodzą nam metody abstrakcyjne.

Metody abstrakcyjne

Metody abstrakcyje, możemy stworzyć jedynie w abstrakcyjnej klasie. Nie posiada ona ciała.
Wymusza jej nadpisanie podczas mechanizmu dziedziczenia, czego nie czyni metoda wirtualna.
Nie może posiadać ona modyfikatora dostępu private.
Zobaczmy przykład:

Spróbujmy w nie abstrakcyjnej klasie stworzyć abstrakcyjna metodę:


  public class Test {
        public abstract string MetodaTestowa(int Liczba); 
    }

abstract1

Rys. Błąd zgłoszony przez kompilator.

Spróbujmy teraz zadeklarować abstrakcyjną metodę z modyfikatorem dostępu private w abstrakcyjnej klasie:


 public abstract class Test {

        private abstract string MetodaTestowa(int Liczba); 
    }

abstract3

Rys. Błąd zgłoszony przez kompilator

Dodajmy do naszej klasy Osoba metodę abstrakcyjną WypiszDaneDodatkowe():


    public abstract class Osoba {
        private string imie = "";
        private string nazwisko = "";
        private int wiek = 1;
        public string Imie
        {
            get
            {
                return imie;
            }

            set
            {
                imie = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public string Nazwisko
        {
            get
            {
                return nazwisko;
            }

            set
            {
                nazwisko = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public int Wiek
        {
            get
            {
                return wiek;
            }

            set
            {
                wiek = (int)SprawdzLiczbe(value);
            }
        }

        protected decimal SprawdzLiczbe(decimal liczba)
        {
            return liczba < 0 ? liczba * (-1) : liczba;
        }
        protected string ZmienPierwszaLitereNaDuza(string s)
        {
            s = s.ToLower();
            if (string.IsNullOrEmpty(s))
            {
                return string.Empty;
            }
            return char.ToUpper(s[0]) + s.Substring(1);
        }

        public void WypiszDaneOsobowe()
        {
            Console.WriteLine("Imie: {0} Nazwisko {1} , Wiek {2}", imie, nazwisko, wiek);

        }

        public abstract void WypiszDaneDodatkowe();        
    }

Wymuszamy teraz implementację tej metody w klasach pochodnych. Co mieliśmy wykonane:


public  class Pracownik: Osoba {    
   
        public void WypiszDaneDodatkowe()
        {
            Console.WriteLine("pracownik znajduje sie na stanowisku: {0} zarabia {1}", stanowisko, pensja);
        }
       
    }

    public class Student : Osoba {
     
        public void WypiszDaneDodatkowe() {

            Console.WriteLine("Student znajduje się na semestrze:{0}",semestr);
            Console.WriteLine("Student uczestniczy w przedmiotach: ");

            foreach (var przedmiot in przedmioty)
            {
                Console.WriteLine(przedmiot);
            }
        }
   
    }

Kompilator jednak zgłosi nam błąd:

abstract4

Rys. Błąd zgłoszony przez kompilator

Dzieje się tak ponieważ implementacja w klasie pochodnej metody abstrakcyjnej odbywa się poprzez nadpisanie tej metody używając słowa kluczowego override. Poprawmy więc nasz kod:
Pełna wersja kodu:


  public abstract class Osoba {
        private string imie = "";
        private string nazwisko = "";
        private int wiek = 1;
        public string Imie
        {
            get
            {
                return imie;
            }

            set
            {
                imie = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public string Nazwisko
        {
            get
            {
                return nazwisko;
            }

            set
            {
                nazwisko = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public int Wiek
        {
            get
            {
                return wiek;
            }

            set
            {
                wiek = (int)SprawdzLiczbe(value);
            }
        }

        protected decimal SprawdzLiczbe(decimal liczba)
        {
            return liczba < 0 ? liczba * (-1) : liczba;
        }
        protected string ZmienPierwszaLitereNaDuza(string s)
        {
            s = s.ToLower();
            if (string.IsNullOrEmpty(s))
            {
                return string.Empty;
            }
            return char.ToUpper(s[0]) + s.Substring(1);
        }

        public void WypiszDaneOsobowe()
        {
            Console.WriteLine("Imie: {0} Nazwisko {1} , Wiek {2}", imie, nazwisko, wiek);

        }

        public abstract void WypiszDaneDodatkowe();        
    }

    public  class Pracownik: Osoba {    
   
        private string stanowisko = "";
        private decimal pensja = 0;    
       
        public string Stanowisko
        {
            get
            {
                return stanowisko;
            }

            set
            {
                stanowisko = ZmienPierwszaLitereNaDuza(value);
            }
        }

        public decimal Pensja
        {
            get
            {
                return pensja;
            }

            set
            {
                pensja = SprawdzLiczbe(value);
            }
        }
       
        public override void WypiszDaneDodatkowe()
        {
            Console.WriteLine("pracownik znajduje sie na stanowisku: {0} zarabia {1}", stanowisko, pensja);
        }

       
    }    

    public class Student : Osoba {
        private int semestr = 0;

        private List<string> przedmioty = new List<string>(); 

        public void DodajPrzedmiot(string przedmiot) {
            przedmioty.Add(przedmiot);
        }

        public void UsunPrzedmiot(string przedmiot) {
            przedmioty.Remove(przedmiot);
        } 

        public override void WypiszDaneDodatkowe() {

            Console.WriteLine("Student znajduje się na semestrze:{0}",semestr);
            Console.WriteLine("Student uczestniczy w przedmiotach: ");

            foreach (var przedmiot in przedmioty)
            {
                Console.WriteLine(przedmiot);
            }
        }
    } 

Dzięki temu, że implementujemy metody abstrakcyjne w klasach pochodnych za pomocą ich nadpisywania możliwe jest ich wywołanie za pomocą zmiennej referencyjnej typu klasy bazowej wskazującej na obiekt klasy pochodnej. Czyli po prostu możemy zastosować polimorficzne wywołanie funkcji.

Zobaczmy przykład:
Nie możemy stworzyć obiektu klasy abstrakcyjnej jednak, możemy stworzyć zmienną referencyjną jej typu która będzie wskazywać na klasę pochodną. Dzięki temu możliwe jest zastosowanie polimorfizmu przy uzyciu klasy abstrakcyjnej.
Przykład:


class Program{
        static void Main(){

            Osoba studentAntek = new Student();
            studentAntek.WypiszDaneDodatkowe();         
            Console.ReadLine();
        }
}

Podsumowanie:
Klasy abstrakcyjnej używamy, gdy chcemy po niej dziedziczyć jednak nie chcemy aby można było tworzyć obiektów tej klasy.
Dzięki tej operacji uzyskujemy także wszystkie zalety mechaniki dziedziczenia. Oraz polimorfizmu. Skracamy znacząco kod.
Gdy pojawią się jakieś nowe wspólne elementy dodajemy je do klasy bazowej. Chroni nas to przed powtarzaniem kodu. Gdy posiadamy nadpisane metody wirtualne lub abstrakcyjne, możemy je wywołać za pomocą zmiennej referencyjnej typu klasy bazowej wskazującej na obiekt typu klasy pochodnej.

Metody abstrakcyjne mogą być zadeklarowane jedynie w abstrakcyjnej klasie. Nie posiadają ciała. Nie mogą posiadać modyfikatora dostępu private. Wymuszają one własną implementację w klasach pochodnych za pomocą nadpisania tej metody używając słowa kluczowego override.
Dzięki czemu możliwe jest ich polimorficzne wywołanie.