:::: MENU ::::

Çok Biçimlilik (Polymorphism)

Bir dilin nesne-yönelimli olabilmesi için o dilde şu 3 özelliğin bulunması gerekir;

  • dilde bir sınıf kavramı olmalıdır
  • türetme kavramı olmalıdır
  • dilde çok biçimlilik olmalıdır


Eğer dilde çok biçimlilik yok; fakat sınıf ve türetme kavramı varsa biz bu tür dillere nesne-tabanlı(object-based) diller diyoruz; ama eğer yukarıdakilerin tümü varsa buna nesne-tabanlı değil nesne-yönelimli(object-oriented) diyoruz.

Biyology de çok biçimlilik(polymorphism) biyology den aktarılmış bir terimdir. Evrim süreci içersinde, canlıların bir takım doku ve organlarında, asıl işlevleri aynı kalmak üzere o canlıların yaşam koşullarına veya özelliklerine göre değişikliklerin oluşturulması anlamına gelir.

Örneğin; kulak pek çok canlıda vardır, duyma işlemini yerine getirir; fakat her canlıda kulak çeşitli biçimlerde farklılık göstermektedir.

Çok biçimlilik değişik bakış açılarıyla 3 şekilde tanımlanabilir:

  • Biyolojik tanım:

Taban sınıfın belli bir metodunun türemiş sınıflarda, o sınıflara göre değişik bir biçimde çalıştırılmasıdır.

  • İşlevsel Tanım(yazılım mühendisliği tanımı):

Çok biçimlilik, türden bagımsız kod parcalarının olusturulması için kullanılan bir terimdir.

  • Aşağı seviyeli tanım:

Çok biçimlilik, önceden yazılmıs kodların daha sonra yazılan kodları çagırabilmesidir.

Çok biçimlilik C# da sanal metodlar ile gerçekleştirilir. Sanal metodlar virtual anahtar sözcüğü ile bildirilir.

public virtual void Foo(int a)

(public virtual ,imzadır. Yani metodun erişim belirleyicisi ve parametrik yapısına imza diyordur..)

virtual anahtar sözcüğü ile erişim belirleyici anahtar sözcük aynı sentax grubunda olduğundan yer değiştirmeli olarak yazılabilir. Yani public virtual yada virtual public olarak yazılabilir. Virtual metodlar private olamaz.

Eğer taban sınıftaki bir sanal metod türemiş sınıfta aynı erişim belirleyicisi ile aynı geri dönüşü türüyle, aynı isimli parametrik yapıyla bildirilirse ve bildirimde “override” anahtar sözcüğü kullanılırsa, bu işleme taban sınıftaki sanal metodun türemiş sınıfta “override” edilmesi demek diyoruz. Örneğin;

class App

{

public static void Main()

{

}

class A

{

public virtual void Foo(int a)

{

}

}

class B : A

{

public override void Foo(int a)

{


}

}

}

Override etme işlemi sırasında sunlara dikkat edilmelidir:

Erişim belirleyicisi aynı olmaldır,

Metodun geridönüş değeri aynı olmalıdır,

Metodun imzası aynı olmalıdır.

Sanal metodlar virtual, override ve abstract anahtar sözcükleriyle bildirilmiş metodlardır. Yani override metod da bir sanal metoddur. Virtual anahtar sözcüğü sanallığı başlatmak için, override ise bitirmek için kullanılır. Sanal bir metod baska bir yerde(türemiş sınıfdan türemiş bir sınıfta) tekrar override edilebilir.

Örnek:


class App

{

public static void Main()

{


}


class A

{

public virtual void Foo(int a)

{


}

}

class B : A

{

public override void Foo(int a)

{



}

}

class C : B

{

public override void Foo(int a)

{


}

}

}

Yani yukları da da görüldüğü üzere; Türemiş sınıfta override edilmiş bir metod, o türemiş sınıflardan türemiş sınıflarda da yeniden override edilebilir.

Taban sınıfta sanal olmayan bir metod override edilemez yani;

        class A
        {
            public /*virtual yok*/ void Foo(int a)            
      {

            }
        }
        class B : A
        {
            public override void Foo(int a)
            {

            }

Benzer biçimde bir sanal metod da türemiş sınıfta override edilmek zorunda değildir.

Override işlemi için sanal metodun, o sınıfın doğrudan taban sınıfında bulunması gerekmez. Yukarıya doğru herhangi bir taban sınıfta metod sanal olarak bildirilmiş ise; override işlemi yapılabilir. Yani;

  class A
        {
            public virtual void Foo(int a)
            {

            }
        }
        class B : A
        {
            public void Foo(int a)
            {

            }
        }
  class C : B
        {
            public override void Foo(int a)
            {

            }
        }

ÇOK BİÇİMLİ MEKANİZMA

“R” bir sınıf türünden referans ve Foo da static olmayan bir metod olmak üzere; r.Foo(); çağırmasında(çağırma işleminde) derleyici önce Foo() metodunu referansın static türüne ilişkin sınıfta ve onun taban sınıflarında arar. Eğer bulursa, metodunun sanal olup olmadıgına bakar. Metod sanal değil ise, bulunan metod çağırılır. Metod sanalsa, referansın dinamik türüne ilişkin sınıfın aynı isimli ve parametrik yapılı sanal metodu çağrılır.

Örneğin;

  class A
        {
            public virtual void Foo(int a)
            {

            }
        }
        class B : A
        {
            public override void Foo(int a)
            {

            }
        }
class App
    {
        public static void Main()
        {
            //...
            
            A x = new A();
            B y = new B();

           //x = y;
           x.Foo();    // B.Foo çağrılır.
 
        }

        class A
        {
            public virtual void Foo()
            {
                Console.WriteLine("A.Foo");
            }
        }
        class B : A
        {
            public override void Foo()
            {
                Console.WriteLine("B.Foo");
            }
        }
    }

class App
    {
        public static void Main()
        {
            //...
            
            A x = new A();
            B y = new B();

x = y; //-->ekran çıktısındaki farkı yaratan bu
 satır // B.Foo çağrılır.
            x.Foo();   
 
        }

        class A
        {
            public virtual void Foo()
            {
                Console.WriteLine("A.Foo");
            }
        }
        class B : A
        {
            public override void Foo()
            {
                Console.WriteLine("B.Foo");
            }
        }
    }

ÇIKTISI:

class App
    {
        public static void Main()
        {
            //...
            
            A x = new A(); 
            B y = new B();
            C z = new C();
            
            Console.WriteLine("\n");
            x.Foo();
            y.Foo();
            z.Foo();

            Console.WriteLine("\n");
            y = z;            
            x.Foo();
            y.Foo();
            z.Foo();

            Console.WriteLine("\n"); 
            x = y;
            x.Foo();   //-->ekran çıktısındaki farkı oluşturan bu satır // B.Foo çağrılır.
            y.Foo();
            z.Foo();
 
        }

        class A
        {
            public virtual void Foo()
            {
                Console.WriteLine("A.Foo");
            }
        }
        class B : A
        {
            public override void Foo()
            {
                Console.WriteLine("B.Foo");
            }
        }
        class C : B
        {
            public override void Foo()
            {
                Console.WriteLine("C.Foo");
            }
        }
    }

Eğer referansın dinamik türüne ilişkin sınıfta çağrılan sanal metod override edilmemişse bu sanal metodun yukarıya doğru override edildiği ilk taban sınıfınki çağırılır.

Bir metod overload(override değil bu yani bir) edilmiş olabilir ve bunlardan yalnızca biri sanal yapılabilir yada diğer istenilenler sanal yapılabilir.

class A
        {
            public void Foo()
            {
                //...
            }
        
        
            public virtual void Foo(int a)
            {
                //...
            }
        
        
            public void Foo(long a)
            {
                //...   
            }
        }

Burada int parametreli metod sanaldır diğerleri değildir.

using System;
using System.Collections;

namespace CSD
{
    class App
    {
        public static void Main()
        {
            ArrayList al = new ArrayList();

            al.Add(new A());
            al.Add(new B());
            al.Add(new C());


            foreach (A x in al)
                x.Foo();
        }

        class A
        {
            public virtual void Foo()
            {
                Console.WriteLine("A.Foo()");
            }
        }
        class B : A
        {
            public override void Foo()
            {
                Console.WriteLine("B.Foo()");
            }
        }

        class C : B
        {
            public override void Foo()
            {
                Console.WriteLine("C.Foo()");
            }
        }

    }
}

Şüphesiz sanal bir metod static olamaz. Bu nedenle. Static anahtar sözcüğü ile virtual ve override anahtar sözcükleri bir arada kullanılamaz.

Örneğin bir methodun parametresi taban sınıf türünden bir referans olabilir ve bu methodda bu referansla sanal method çağırılmış olabilir.


		public void Foo(A a)
		{
			//...
			a.Foo();
			//...
		}	

Biz bu methodu farklı türemiş sınıf türleriyle çağırabiliriz.

Örneğin:

using System;

namespace CSD
{
	class App
	{
		public static void Main()
		{
			Test(new A());
			Test(new B());
			Test(new C());
			Test(new B());
		}

		public static void Test(A a)
		{
			//...
			a.Foo();
			//...
		}
	}

	class A
	{
		public virtual void Foo()
		{
			Console.WriteLine("A.Foo()");
			
		}
	}
	class B : A
	{
		public override void Foo()
		{
			Console.WriteLine("B.Foo()");
		}
	}
	class C : B
	{
		public override void Foo()
		{
			Console.WriteLine("C.Foo()");
			
		}
	}
}

Object sınıfının bazı sanal methodları vardır. Örneğin ToString() sanal methodu şöyledir;

public virtual string ToString()

Bu methodun parametresi yoktur fakat bize geri dönüş değeri olarak bir string verir. Object sınıfının bu methodunu biz tüm sınıflarda override edebiliriz.

using System;

namespace CSD
{
	class App
	{
		public static void Main()
		{
			Sample s = new Sample();
			object o;

			o = s;

			Console.WriteLine(o.ToString());
		}
	}

	class Sample
	{
		public override string ToString()
		{
			return "This is a Test";
		}
	}
}
using System;
using System.Collections;

namespace CSD
{
	class App
	{
		public static void Main()
		{
			ArrayList al = new ArrayList();
			al.Add(new A());
			al.Add(new B());
			al.Add(new C());
			//...

			foreach (object o in al)
				Console.WriteLine(o.ToString());
		}
	}
	class A
	{
		public override string ToString()
		{
			return "A Class";
		}
	}
	class B
	{
		public override string ToString()
		{
			return "B Class";
		}
	}
	class C
	{
		public override string ToString()
		{
			return "C Class";
		}
	}
}

Console Sınıfının Object parametreli bir Write ve WriteLine methodu vardır. Bu methodlar aldıkları object parametresiyle ToString() sanal methodunu çağırıp, elde edilen yazıyı ekrana yazdırırlar. Bu durumda örneğin;

using System;

namespace CSD
{
	class App
	{
		public static void Main()
		{
			Sample s = new Sample();

			Console.WriteLine(s);
		}
	}
	class Sample
	{
		public override string ToString()
		{
			return "This is a Test";
		}
	}
}

Bu işlemin eşdeğeri şöyledir;

using System;

namespace CSD
{
	class App
	{
		public static void Main()
		{
			Sample s = new Sample();

			Console.WriteLine(s.ToString());
		}
	}
	class Sample
	{
		public override string ToString()
		{
			return "This is a Test";
		}
	}
}

Anımsanacağı gibi yapılar türetmeye kapalıdır. Fakat yapılarda taban object sınıfının sanal methodları yine override edilebilir.

Örneğin:


using System;

namespace CSD
{
	class App
	{
		public static void Main()
		{
			Sample s = new Sample();

			Console.WriteLine(s.ToString());
		}
	}

	struct Sample
	{
		public override string ToString()
		{
			return "This is a Test";
		}
	}
}
using System;

namespace CSD
{
	class App
	{
		public static void Main()
		{
			DateTime dt = new DateTime(2000, 2, 14);

			object o = dt;

			Console.WriteLine(dt);
		}
	}
}

.NET içerisinde neredeyse tüm sınıflarda ve yapılarda ToString() sanal methodu override edilmiştir. Bu tür string methodları bize o nesneyi temsil eden bir yazı vermektedir. Tabi bu yazı tam istediğimiz gibi olmayabilir. Fakat bir takım şeyleri test amaçlı ekrana bastırmak için bu ToString methodları kullanılabilir.

Peki biz kendi sınıf yada yapımızda ToString() methodunu override etmezsek ne olur?
Bu durumda object sınıfının ToString() methodu çağırılır. Object sınıfının ToString() methoduda bize isim alanıyla kombine edilmiş bir biçimde sınıfın ismine ilişkin bir yazı verir.

using System;

namespace CSD
{
	class App
	{
		public static void Main()
		{
			Sample s = new Sample();

			Console.WriteLine(s);
		}
	}

	class Sample
	{
		//...
	}
}

Int, Long, Double gibi yapılar için ToString() sanal methoduda yine override edilmiştir. Bu methodlar bize ilgili sayının yazı karşılığını verir. O halde .NET’ te bir sayıyı yazıya dönüştürmek için ToString() methodu kullanılır.


using System;

namespace CSD
{
	class App
	{
		public static void Main()
		{
			int a = 123;

			object o = a;
			Console.WriteLine(o);
		}
	}
}


using System;
using System.Collections;

namespace CSD
{
	class App
	{
		public static void Main()
		{
			ArrayList al = new ArrayList();

			al.Add(123);
			al.Add(34.5);
			al.Add("Ali");
			al.Add('x');

			foreach (object o in al)
				Console.WriteLine(o);
		}
	}
}

Bir string referansıyla başka bir tür doğrudan + operatörüyle toplanabilir. Yani s bir string referansı a da herhangi bir türden olmak üzere, s + a ifadeside a + s ifadeside geçerlidir. Bu durumda derleyici string olmayan operant üzerinde ToString() sanal methodunu çağırır ve buradan elde edilen yazıyı birleştirir.

using System;
using System.Collections;

namespace CSD
{
	class App
	{
		public static void Main()
		{
			string s;
			int a = 123;

			s = "Sayı = " + a;
			Console.WriteLine(s);
		}
	}
}


using System;
using System.Collections;

namespace CSD
{
	class App
	{
		public static void Main()
		{
			string str;
			Sample s = new Sample();
			
			str = "Test " + s;
			Console.WriteLine(str);
		}
	}

	class Sample
	{
		//...
	}
}

ToString() methodunun sayısal değerler için tersi static Parse methodlarıdır. Pars methodları yazıyı alıp bize onu sayıya dönüştürüp verir.

using System;
using System.Collections;

namespace CSD
{
	class App
	{
		public static void Main()
		{
			int val = int.Parse("123");

			Console.WriteLine(val);
		}
	}
}

Çok biçimli mekanizmanın değişik bir uygulamasıda şöyle olabilir:
Taban sınıfın sanal olmayan bir methodu sanal methodu çağırsın;

	class A
	{
		public void Foo()
		{
			//...
			Bar();
			//...
		}
		public virtual void Bar()
		{
			//...
		}
		//...
}

Biz bu sınıftan sınıf türetip sanal methodu override edelim;

	class B : A
	{
		public override void Bar()
		{
			//...
		}
		//...
}

Şimdi türemiş sınıf türünden bir nesne yaratıp taban sınıfın sanal olmayan methodunu çağıralım;

			B b = new B();
 	 	        b.Foo();

Bu durumda Foo methodunun içerisinde kullanılan this referansının static türü A, dinamik türü B dir. Bar() ile this.Bar() eşdeğer olduğuna göre burada türemiş B sınıfının Bar() methodu çağırılacaktır.

İçerisinde kullanılan this referansının static türü A, dinamik türü B dir. Bar() ile this.Bar() eşdeğer olduğuna göre burada türemiş B sınıfının Bar() methodu çağırılacaktır.

Kaynak:Kaan Aslan Hocanın Dersinde Tuttuğum Ders Notlarıdır.


2 Comments

So, what do you think ?