C Sharp Programlama Dili/Operatör aşırı yükleme

Vikikitap, özgür kütüphane
Ders 12. Operatör aşırı yükleme


Şimdiye kadar kendi sınıflarımızı, metotlarımızı, özelliklerimizi yazdık. Şimdi kendi operatörlerimizi yazmaya sıra geldi. Şimdi içinde saat bilgileri olan bir sınıf tasarlayacağız:

 using System;
 class Zaman
 {
    public int Saat;
    public int Dakika;
    public int Saniye;
    public Zaman(int saat, int dakika, int saniye)
    {
        Dakika=dakika+saniye/60;
        Saniye=saniye%60;
        Saat=saat+Dakika/60;
        Dakika=Dakika%60;
    }
 }

Gördüğünüz gibi Zaman sınıfının Saat, Dakika ve Saniye olmak üzere üç özelliği var ve bu özellikler nesne yaratılırken girilmek zorunda. Dakika ve saniye 60'tan büyük olamayacağı için yapıcı metotta gerekli düzenlemeler yapılıyor. Bu sınıftan bir nesne yarattığımızda söz konusu nesnenin Saat, Dakika ve Saniye olmak üzere üç özelliği olacak. Başka bir nesne yarattığımızda da aynı şekilde bunun da Saat, Dakika ve Saniyesi olacak. Peki Zaman türünden iki nesnenin + operatörüne sokularak bu iki zaman diliminin toplanmasını ister miydiniz? Örneğin zaman1 ve zaman2 Zaman türünden iki nesne olsun. zaman1+zaman2 yazdığımızda karşılıklı dakika ve saniyeler toplansın ve saniyeden dakikaya ve dakikadan saate gerekli aktarımlar yapılsın. İşte şimdi bunu yapacağız:

 using System;
 class Zaman
 {
    public int Saat;
    public int Dakika;
    public int Saniye;
    public Zaman(int saat, int dakika, int saniye)
    {
        Dakika=dakika+saniye/60;
        Saniye=saniye%60;
        Saat=saat+Dakika/60;
        Dakika=Dakika%60;
    }
    public static Zaman operator+(Zaman a, Zaman b)
    {
        int ToplamSaniye=a.Saniye+b.Saniye;
        int ToplamDakika=a.Dakika+b.Dakika;
        int ToplamSaat=a.Saat+b.Saat;
        return new Zaman(ToplamSaat,ToplamDakika,ToplamSaniye);
    }
 }        
 class AnaProgram
 {
    static void Main()
    {
       Zaman zaman1=new Zaman(5,59,60);
       Zaman zaman2=new Zaman(1,0,120);
       Zaman zaman3=zaman1+zaman2;
       Console.WriteLine("{0}.{1}.{2}",zaman3.Saat,zaman3.Dakika,zaman3.Saniye);
    }
 }

Bu program ekrana 7.2.0 yazacaktır. Gördüğünüz gibi kendi operatörlerimizi yazmak, daha doğrusu + operatörüne aşırı yükleme yapmak çok da zor değil. Operatör metotlarımızın adı operator+ mantığında olmalı. Örneğin söz konusu operatörümüz - olsaydı operatör metodumuzun adı operator- olacaktı. Operatörümüzün alacağı operandları bu metoda parametre olarak gönderiyoruz. Operatör metodunun tuttuğu değer operatörün operandlarıyla birlikte tuttuğu değeri ifade eder. return new Zaman(ToplamSaat,ToplamDakika,ToplamSaniye); satırıyla metodun, dolayısıyla da operatörün verilen parametreler ölçüsünde Zaman türünden bir nesne tutmasını sağladık. Ancak şimdi Zaman türünden bir nesneyle int türünden bir nesneyi toplamamız mümkün değildir. Eğer Zaman türünden bir nesneyle int türünden bir nesne toplanmak istenirse int türünden nesneyi saniye olarak hesaba katabiliriz. Bunun içinse Zaman sınıfına şu operatör metodunu ekleyebiliriz:

 public static Zaman operator+(Zaman a, int b)
 {
   int ToplamSaniye=a.Saniye+b;
   return new Zaman(a.Saat,a.Dakika,ToplamSaniye);
 }

Gördüğünüz gibi bu kadar basit. Ayrıca şunu hatırlatmak isterim: return new Zaman(a.Saat,a.Dakika,ToplamSaniye) satırıyla metodun Zaman türünden verilen parametrelere göre yeni bir nesne oluşturulup bu nesneyi tutmasını sağladık. Dolayısıyla Zaman sınıfının yapıcı metodu çalıştırıldı. Dolayısıyla da bizim tekrar Saniye, Dakika ve Saat arasında aktarım yapmamıza gerek kalmadı. Operatör metotlarıyla ilgili bilmeniz gerekenler:

  • Operatör metotlarının geri dönüş tipinin illaki söz konusu sınıf tipinden olmasına gerek yoktur. Örneğin Zaman sınıfındaki ilk toplama operatör metodunu şöyle değiştirebiliriz.
 public static string operator+(Zaman a, Zaman b)
 {
     int ToplamSaniye=a.Saniye+b.Saniye;
     int ToplamDakika=a.Dakika+b.Dakika;
     int ToplamSaat=a.Saat+b.Saat;
     Zaman nesne=new Zaman(ToplamSaat,ToplamDakika,ToplamSaniye);
     return nesne.Saat+"."+nesne.Dakika+"."+nesne.Saniye;
 }

Burada Zaman nesne=new Zaman(ToplamSaat,ToplamDakika,ToplamSaniye); satırını ekleyerek Saat, Dakika, Saniye aktarımlarının yapılmasını sağladık. Bu metoda göre Zaman tipinden a ve b nesneleriyle yapılan a+b ifadesinin sonucu string tipinde olur.

  • Operatör metotları static ve public olmalıdır.
  • Dört işlem operatörleri (+, -, *, /) herhangi bir koşul olmaksızın aşırı yüklenebilirler.
  • Tek operand alan operatörlerin parametresi mutlaka ilgili sınıf türünden olmalıdır. İki operand alan operatörlerin parametrelerinden en az birisi ilgili sınıf türünden olmalıdır.
  • Operatör metotlarında ref ve out anahtar sözcükleri kullanılamaz.
  • İki operand alan operatörlerin operandlarının sırası operatör metodundaki parametrelerin sırasıyla aynı olmalıdır. Örneğin operator+(Zaman z, int i) operatör metodunu oluşturmuşsak 5+new Zaman() ifadesini programımız içinde kullanamayız. Bu ifadeyi kullanabilmemiz için operator+(int i, Zaman z) operatör metodunu oluşturmalıyız.

İlişkisel operatörlerin aşırı yüklenmesi[değiştir]

Şimdi Zaman sınıfına aşağıdaki operatör metotlarını ekleyelim.

 public static bool operator==(Zaman a, Zaman b)
 {
     if(a.Saniye==b.Saniye&&a.Dakika==b.Dakika&&a.Saat==b.Saat)
         return true;
     else
        return false;
 }
 public static bool operator!=(Zaman a, Zaman b)
 {
     return !(a==b);
 }

İlişkisel operatörlerdeki ana kural ilişkisel operatörlerin birbirlerinin zıtlarının sınıf içinde ve aynı türde olmasının zorunlu olmasıdır. Yani biz burada yukarıdaki metotların yalnızca bir tanesini yazıp bırakamazdık ya da birininin geri dönüş tipini bool, birinin int yapamazdık. Ayrıca return !(a==b); satırı son derece mümkündür. Burada daha önce yazılmış bir metodu kullandık. Her iki metot da static olduğu için herhangi bir nesne tanımlamaya gerek yoktur. İlişkisel operatörlerin geri dönüş tipi bütün türlerde olabilmesine rağmen bool olması tavsiye edilir. Diğer ilişkisel operatörler olan <, >, <= ve >= operatörlerini de çift hâlinde olmak şartıyla tıpkı örneğimizdeki gibi aşırı yükleyebiliriz.

true ve false operatörlerinin aşırı yüklenmesi[değiştir]

Şimdiye kadar true ve falseu bool türünden sabitler olarak gördük. Ancak şimdi bu true ve falseu operatörmüş gibi aşırı yükleyebileceğiz. Şimdi şu metotları Zaman sınıfına ekleyin:

 public static bool operator true(Zaman a)
 {
     if(a.Saat>12)
         return true;
     else
        return false;
 }
 public static bool operator false(Zaman a)
 {
    if(a.Saat<=12)
        return true;
    else
        return false;
 }

Bu metotları asıl programımız içinde şöyle kullanabiliriz:

 Zaman zaman1=new Zaman(5,59,60);
 Zaman zaman2=new Zaman(2,35,40);
 if(zaman1)
    Console.WriteLine("Öğleden sonra");
 else
    Console.WriteLine("Öğleden önce");
 if(zaman2)
    Console.WriteLine("Öğleden sonra");
 else
    Console.WriteLine("Öğleden önce");

Gördüğünüz gibi derleyici normalde true ya da false olması gereken bir yerde Zaman türünden bir nesne gördüğü zaman Zaman sınıfındaki true ve false metotlarını çalıştırıyor ve söz konusu Zaman nesnenisinin true ya da false değerlerden birisini tutmasını sağlıyor. Eğer böyle bir yapı oluşturmak istiyorsak hem true hem de false metotlarının sınıfımız içinde bulunması gerekir. Ayrıca true ve false operatör metotlarının geri dönüş tipi mutlaka bool türünden olmalıdır.

Mantıksal operatörlerin aşırı yüklenmesi[değiştir]

Örnek (sınıfımıza eklenecek):

 public static bool operator|(Zaman a,Zaman b)
 {
    if(a.Saat>12||b.Saat>12)
       return true;
    else
       return false;
 }

& ve |, ! operatörlerinin aşırı yüklenebilmesi için herhangi bir şart yoktur. (! operatörünün tek operand aldığını unutmayın.) Ancak && ve || operatörlerinin aşırı yüklenebilmesi için söz konusu sınıf için şu şartların sağlanması gerekir:

  • & ve | operatörleri aşırı yüklenmiş olmalıdır.
  • true ve false operatörleri aşırı yüklenmiş olmalıdır.
  • operator& ve operator| operatörlerinin parametreleri ilgili sınıf türünden olmalıdır.
  • operator& ve operator| operatörlerinin geri dönüş tipi ilgili sınıf türünden olmalıdır.
  • Yukarıdaki şartlar sağlandığı takdirde bizim ayrıca && ve || operatörlerini aşırı yüklememize gerek kalmaz. Yukarıdaki şartlar sağlandığı takdirde sanki && ve || operatörleri aşırı yüklenmiş gibi kodumuzu yazabiliriz. Örnek:
 using System;
 class Sinif
 {
    public int Sayi;
    public Sinif(int sayi)
    {
       Sayi=sayi;
    }
    public static bool operator true(Sinif a)
    {
       return true;
    }
    public static bool operator false(Sinif a)
    {
       return false;
    }
    public static Sinif operator&(Sinif a,Sinif b)
    {
       return new Sinif(20);
    }
    public static Sinif operator|(Sinif a,Sinif b)
    {
       return new Sinif(30);
    }
 }
 class AnaProgram
 {
    static void Main()
    {
       Sinif a=new Sinif(50);
       Sinif b=new Sinif(10);
       Console.WriteLine((a||b).Sayi);
       Console.WriteLine((a&&b).Sayi);
    }
 }

Yukarıdaki programda ekrana alt alta 50 ve 20 yazar. Peki neden 50 ve 20 yazdı? İşte mantığı:

true operatörü false operatörü a||b a&&b
true false ilk operand & operatör metodu
false true | operatör metodu ilk operand
true true ilk operand ilk operand
false false | operatör metodu & operatör metodu

Tablodan da görebileceğiniz gibi örneğimizde true operatörü true, false operatörü false değer ürettiği için a||b ifadesi ilk operand olan a'yı, a&&b ifadesi ise & operatör metodunun geri dönüş değerini tutar.

Başka bir örnek:

using System;
class Program
{
    static void Main()
    {
        Program p1 = new Program { Sayi = -5 };
        Program p2 = new Program { Sayi = 8 };
        Program p3 = new Program { Sayi = 11 };

        //|| işlemleri
        Console.WriteLine((p3 || p1).Sayi);
        Console.WriteLine((p3 || p2).Sayi);
        Console.WriteLine((p1 || p2).Sayi);

        //&& işlemleri
        Console.WriteLine((p3 && p1).Sayi);
        Console.WriteLine((p3 && p2).Sayi);
        Console.WriteLine((p1 && p2).Sayi);
    }
    public int Sayi;
    public static bool operator true(Program p)
    {
        if (p.Sayi >= 0)
            return true;
        else
            return false;
    }
    public static bool operator false(Program p)
    {
        if (p.Sayi % 2 == 0)
            return true;
        else
            return false;
    }
    public static Program operator&(Program p1,Program p2)
    {
        return new Program { Sayi = p1.Sayi * p2.Sayi };
    }
    public static Program operator|(Program p1,Program p2)
    {
        return new Program { Sayi = p1.Sayi + p2.Sayi };
    }
}

Bir önceki örnekte true ve false operatörleri her durumda true veya false döndürüyordu. Bu örnekte ise true ve false operatörlerinin döndürdüğü değer nesneye bağlı. Buradaki nesne ise || veya && operatörünün hemen solundaki nesne. Bu örnekte ayrıca true ve false operatörleri birbirlerini tümleyen yapıda değil. Yani true true döndürdüğünde, false'un false döndürme (ve tersi) şartı yok. Şimdi isterseniz bu örnekteki || ve && operatörlerine sokulan Program türündeki nesnelerin nasıl sonuç döndürdüğünü detaylandıralım:

p3 || p1 : p3 için true operatörü true döndürür. p3 için false operatörü false döndürür. true'nun true, false'un false döndürdüğü kombinasyonda || operatörü için ilk operandı seçiyoruz. (p3=11)

p3 || p2 : p3 için true operatörü true döndürür. p3 için false operatörü false döndürür. true'nun true, false'un false döndürdüğü kombinasyonda || operatörü için ilk operandı seçiyoruz. (p3=11)

p1 || p2 : p1 için true operatörü false döndürür. p1 için false operatörü false döndürür. Her iki operatörün de false döndürdüğü kombinasyonda || operatörü için | operatör metodunun aynı operandlarla döndürdüğü değer seçilir. (-5)|8=3

p3 && p1 : p3 için true operatörü true döndürür. p3 için false operatörü false döndürür. true'nun true, false'un false döndürdüğü kombinasyonda && operatörü için & operatör metodunun aynı operandlarla döndürdüğü değer seçilir. 11&(-5)=-55

p3 && p2 : p3 için true operatörü true döndürür. p3 için false operatörü false döndürür. true'nun true, false'un false döndürdüğü kombinasyonda && operatörü için & operatör metodunun aynı operandlarla döndürdüğü değer seçilir. 11&8=88

p1 && p2 : p1 için true operatörü false döndürür. p1 için false operatörü false döndürür. Her iki operatörün de false döndürdüğü kombinasyonda && operatörü için & operatör metodunun aynı operandlarla döndürdüğü değer seçilir. (-8)&5=(-40)

Tür dönüşüm operatörünün aşırı yüklenmesi[değiştir]

Hatırlarsanız C#'ta iki tane tür dönüşüm mekanizması vardı. Bunlardan birincisi bilinçli, ötekisi de bilinçsiz tür dönüşümüydü. Şimdi bu iki olayın kendi sınıflarımız türünden nesneler için de gerçekleşmesini sağlayacağız. Bu iki olayın taslağı şu şekildedir:

 public static implicit operator HedefTur(KaynakTurdekiNesne)
 {
    return HedefTurdenVeri;
 }

Bu bilinçsiz tür dönüşümü içindi. Şimdi de bilinçli tür dönüşümü metodunun taslağı:

 public static explicit operator HedefTur(KaynakTurdekiNesne)
 {
    return HedefTurdenVeri;
 }

Şimdi bu taslakları örneklendirelim:

 using System;
 class Sinif
 {
    public int Sayi;
    public Sinif(int sayi)
    {
       Sayi=sayi;
    }
    public static implicit operator int(Sinif a)
    {
       return a.Sayi;
    }
 }
 class AnaProgram
 {
    static void Main()
    {
       Sinif a=new Sinif(50);
       int b=a;
       Console.WriteLine(b);
    }
 }

Şimdi de bilinçli tür dönüşümünü örneklendirelim:

 using System;
 class Sinif
 {
    public int Sayi;
    public Sinif(int sayi)
    {
       Sayi=sayi;
    }
    public static explicit operator int(Sinif a)
    {
       return a.Sayi;
    }
 }
 class AnaProgram
 {
    static void Main()
    {
       Sinif a=new Sinif(50);
       int b=(int)a;
       Console.WriteLine(b);
    }
 }

Gördüğünüz gibi bilinçli ve bilinçsiz tür dönüşümü operatör metotlarını yazarken tek değişen implicit ve explicit anahtar sözcükleri. Bir sınıf içinde parametre ve geri dönüş tipi aynı olan explicit ve implicit metotlar aynı anda bildirilemez. Eğer implicit metodu bildirmişsek explicit (bilinçli) tür dönüşümüne de izin vermiş oluruz. İlk örneğimizde (implicit) a nesnesini int türünden değişkenlerin kullanılabildiği her yerde kullanabiliriz. İkinci örneğimizde ise aynı şeyi tür dönüştürme operatörünü kullanarak yapabiliriz. implicit ve explicit metotlarla yalnızca Sinif türünden başka türlere ya da başka türlerden Sinif türüne dönüşüm yapabiliriz. Yani örneğin byte ile int arasındaki dönüşüm mekanizmasına müdahale edemeyiz.

BİLGİLENDİRME: Şimdi konunun başında gördüğümüz Zaman sınıfını düşünün. Artık implicit metot sayesinde Zaman türünden bir nesneye a="12.45.34"; şeklinde değer atayabilirsiniz. Bunun için Zaman sınıfının implicit metodu içinde gerekli parçalamaları yapar ve sonucu bir Zaman nesnesinde tutarsınız. Gördüğünüz gibi adeta kendi sabitlerimizi yazabiliyoruz. Bunun gibi daha birçok şey hayal gücünüze kalmış.

NOT: Artık bu öğrendiğimiz implicit metot sayesinde Sinif türünden bir nesneyi new anahtar sözcüğünü kullanmadan direkt Sinif a=23; gibi bir şey yazarak oluşturabiliriz. Ancak bunun için tabii ki sınıfımız içinde int türünden Sinif türüne implicit dönüşüm metodunu oluşturmamız gerekir.

NOT: Bildiğiniz gibi C#'ta bazı türler arasında bilinçsiz tür dönüşümü olur. Örneğin C#'ta byte türünden inte bilinçsiz tür dönüşümü mümkündür. Ancak biz programımızı yazarken bunları kafamıza takmamıza gerek yok. Yani Sinif türünden int'e dönüşüm yapan metot oluşturmuşsak Sinif türünden byte'a dönüşüm yapan metot oluşturamayız diye bir kural yoktur. İstediğimiz gibi tür dönüşüm metotlarını yazabiliriz. Yeter ki bir türden bir türe dönüşüm yapan birden fazla metot oluşturmayalım ve dönüşümün taraflarından bir tanesi Sinif türünden olsun.

NOT: Aynı anda birden fazla bilinçsiz tür dönüşümü olabilir. Örneğin a, Sinif tipinden bir nesne olmak üzere eğer Sinif içinde Sinif'tan int'e bilinçsiz tür dönüşümünü sağlayan implicit metot mevcutsa aşağıdaki satır geçerli olacaktır:

double d=a;

Burada Sinif tipinden olan a nesnesi önce int'e, sonra double'a bilinçsiz olarak dönüşmüştür.

Operatör aşırı yüklemeyle ilgili son notlar[değiştir]

Atama operatörünü (=) aşırı yükleyemeyiz. Çünkü zaten gerek yoktur. Biz atama operatörünü aşırı yükleyelim veya yüklemeyelim zaten kullanabiliriz. İşlemli atama operatörlerinde ise şöyle bir kural vardır: Örneğin + operatörünü aşırı yüklemişsek += operatörünü de kullanabiliriz. Bu durum bütün işlemli atama operatörlerinde geçerlidir. Hiçbir operatörün öncelik sırasını değiştiremeyeceğimiz gibi temel veri türleri (string, int, vb.) arasındaki operatörlere de müdahale edemeyiz. Bu konuda işlenenler dışında hiçbir operatörü aşırı yükleyemeyiz. Örneğin dizilerin elemanlarına erişmek için kullanılan [] operatörünü, yeni bir nesneye bellekte yer açmak için kullanılan new operatörünü, ?: operatörünü, vb. aşırı yükleyemeyiz.

Bu kitabın diğer sayfaları
  • Sınıflar
  • Operatör aşırı yükleme
  • İndeksleyiciler
  • Yapılar
  • Enum sabitleri
  • İsim alanları
  • System isim alanı
  • Temel I/O işlemleri
  • Temel string işlemleri
  • Kalıtım
  • Arayüzler
  • Partial (kısmi) tipler
  • İstisnai durum yakalama mekanizması
  • Temsilciler
  • Olaylar
  • Önişlemci komutları
  • Göstericiler
  • Assembly kavramı
  • Yansıma
  • Nitelikler
  • Örnekler
  • Şablon tipler
  • Koleksiyonlar
  • yield
  • Veri tabanı işlemleri
  • XML işlemleri
  • Form tabanlı uygulamalar
  • Visual Studio.NET
  • Çok kanallı uygulamalar
  • ASP.NET