Herkese merhaba. 😊 Bu yazıda modüler programlamanın ilk basamağı olan metotların biraz daha içine gireceğiz. Metotlarda kullanılan ref ve out anahtar kelimelerine, method overloading yöntemine ve recursive (rekürsif) metotlara yakından bakacağız. C# üzerinde metotlar oluştururken bunları çok yoğun bir şekilde olmasa da ara ara pratiklik kazandırması için bunları kullanıyoruz. Lafı daha fazla uzatmadan hemen konuya geçelim.

Metotlar

Ref ve Out

Ref ve out keywordlerini metotların parametreler kısmında kullanıyoruz. Bildiğimiz gibi C# içerisinde hem değer (value) tipli hem de referans (reference) tipli olmak üzere iki adet veri türü vardır. Metot içerisine parametre yolladığımızda da parametre geçme işlemi “pass by value” veya “pass by reference” yoluyla yapılır. Pass by value yönteminde gönderilen parametrenin bir kopyası oluşturulur ve metot içerisinde yapılan değişiklikliklerden sadece bu kopya etkilenir. Parametre olarak gönderdiğimiz orijinal değişken etkilenmez. Pass by reference yönteminde gönderilen parametrenin ram’de bulunduğu adres gönderilir. Bu sayede metot içerisindeki değişikliklerden orijinal değişken etkilenir. Şimdi bir örnek ile bu durumu kavrayalım.

static void Main(string[] args)
{
    int sayi = 50;
    string deger = "ESKİ";

    Console.WriteLine("Before value type {0}", sayi);
    Console.WriteLine("Before reference type {0}", deger);
    Console.WriteLine("-----------");

    PassValueType(sayi);
    PassReferenceType(deger);

    Console.WriteLine("After value type {0}", sayi);
    Console.WriteLine("After reference type {0}", deger);
    Console.WriteLine("-----------"); 

    Console.ReadKey();
}

static void PassValueType(int deger)
{
    deger = 100;
}

static void PassReferenceType(string deger)
{
    deger = "Yeni";
}

Reference ve Value Type Parametre

Örnekte gördüğümüz gibi iki değer de pass by value yöntemi ile gönderildi. String referans tipli olmasına rağmen eski değerini koruyor. Bunun nedeni string’in immutable referans tipli olmasıdır. Yani bir kere oluşturulduktan sonra değeri değiştirilemez. Pass by reference yöntemi ile parametre göndermek için ref ve out anahtar kelimelerine ihtiyaç duyuyoruz.

ref

Metoda geçtiği parametrenin kopyasını değil orijinal değişkenin adresini gönderir. Parametre ref tanımlandıysa çağırılırken de ref tanımlanması gerekir. Değişkenin adresini ref keyword’ü bulur biz göremeyiz. Ref kullanılırken gönderilecek parametrenin mutlaka ilk değerinin tanımlanmış olması gerekiyor. Ref kullanımına giriş yapalım.

static void Main(string[] args)
{   
    int number = 10;
    Console.WriteLine("Önce: {0}", number);
    Change(ref number);
    Console.WriteLine("Sonra: {0}", number);
    number = 158;
    Console.WriteLine("Main Değişim: {0}", number);
    Console.ReadKey();
}
static void Change(ref int number)
{
    number = 50;
}

Ref Intro

Örnekte gördüğümüz gibi ilk değeri 10 olan değişken Change metoduna girince 50 değerine sahip oluyor. Adres göndererek değişkeni değiştirdiğimiz için Main metodu içerisinde ekrana 50 olarak yazdırabiliyoruz. Daha sonradan main içerisinde 158 değerini verip tekrardan değiştirebiliyoruz. Örnek olarak iki string değişkenin değerini değiştiren bir metot yazalım.

static void Main(string[] args)
{
    string value1 = "Kedi", value2 = "Köpek";
    Console.WriteLine("Before: {0} - {1}", value1, value2);
    Swap(ref value1, ref value2);
    Console.WriteLine("After: {0} - {1}", value1, value2);
    Console.ReadKey();
}
static void Swap(ref string text1, ref string text2)
{
    string temp = string.Empty;

    temp = text1;
    text1 = text2;
    text2 = temp;
}

Ref Ornek

out

Mantık olarak ref ile aynı yapıya sahiptir. Parametrenin adresini metoda göndererek orijinal değişken üzerinde değişiklik yapılmasını sağlar. Tek farkı out ile gönderdiğimiz değişkenin ilk değerinin tanımlanmasına gerek yoktur. Üzerinde değişiklik yapmayacağımız ve birden fazla değer geri döndürmek istediğimiz metotlar yazarken out keyword’ünü kullanabiliriz.

static void Main(string[] args)
{
   char letter;
   string text = GetWordAndFirstLetter(out letter);
   Console.ReadKey();
}
static string GetWordAndFirstLetter(out char letter)
{
    string quote = "Kendin ol, diğer herkesten zaten çokça var. O.W";
    letter = quote[0];
    return quote;
}

Out Intro

Bu örnekte Oscar Wilde’ın sevdiğim bir sözünü kullandım. 😊 Out ve Ref keywordlerini bu şekilde bir metot içerisinden iki değer geri döndürmek istediğinizde kullanabilirsiniz. Out ile ilgili bir örnek daha yapalım. Ondalık sayıların tam ve küsurat kısmını ayıran bir metot oluşturalım.

static void Main(string[] args)
{
   Console.Write("Parçalanacak Ondalıklı Sayı: ");
   decimal num = decimal.Parse(Console.ReadLine());
   decimal floating;
   int integer = GetParts(num, out floating);
   Console.WriteLine("Tam Kısım = {0}  -  Ondalıklı Kısım = {1}", num, floating);

   Console.ReadKey();
}
static int GetParts(decimal value, out decimal floating)
{
    int number = (int)value;
    floating = value - number;
    return number;
}

Out Örnek

Ref ve out için oluşturduğum Array Resize ve Custom TryParse metotları için github repoma bakabilirsiniz.

Method Overloading

Metot İmzası

Method overloading’in ne olduğunu anlatmadan önce metot imzasını anlatmak istiyorum. Metot imzası; bir metodun adı ve aldığı parametrelerden oluşur. Metodun geri dönüş tipi imzaya dahil değildir. C# içerisinde birebir aynı isimle ve aynı tipte ve sayıda parametre alan bir metot yazmamıza izin vermez. Örneğin;

Metot İmzası

Burda ikinci Toplama metodunun geri dönüş tipi double olmasına rağmen aynı isim ve aynı sayıda parametre olduğu için ikinci metodun altını çizdi. Burada bu kırmızılığı iki yöntemle giderebiliriz. İlk yöntem metodun adını değiştirmek. Eğer bu metotlar içerisinde aynı işlemi yapmayacaksa o zaman isim değiştirmek daha mantıklı bir yaklaşım olur. İkinci yöntem ise metot overloading yapmak olur. -Bunun Türkçe ismi Metot Aşırı Yükleme gibi saçma bir çeviri o nedenle bunu kullanmayacağım.-

Method Overloading

Tamamen aynı metot imzasına sahip olmayan ama aynı isme sahip olan metotlar oluşturabiliriz. Mesela elimizde şöyle bir senaryo olsun; Toplama metodunun 2 parametreli, 3 parametreli, birden fazla parametreli ve double sayıları toplayıp geri döndürmesine ihtiyacım var. Böyle bir durumda bütün bu ihtiyaçlarımı tek bir metot içerisinde sıkıştırıp yazmak yerine farklı farklı metotlar kurup ihtiyacım olanı çağırıyorum. Bu işleme method overloading deniyor.

static void Main(string[] args)
{
   Console.WriteLine("1. Metot: {0} ", Toplama(1,5));
   Console.WriteLine("2. Metot: {0} ", Toplama(101.65,5.5));
   Console.WriteLine("3. Metot: {0} ", Toplama(78,52,74));
   Console.WriteLine("4. Metot: {0} ", Toplama(125, 74, 25));

   Console.ReadKey();
}
static int Toplama(int sayi1, int sayi2)
{
    return sayi1 + sayi2;
}

static double Toplama(double sayi1, double sayi2)
{
    return sayi1 + sayi2;
}

static int Toplama(int sayi1, int sayi2, int sayi3)
{
    return sayi1 + sayi2 + sayi3;
}

static decimal Toplama(params int[] sayilar)
{
    int toplam = 0;

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

    return toplam;
}

Method Overloading

Yazarken de aşağıdaki gibi Visual Studio bize metodun overloadlarını gösteriyor. Yön tuşları ile methotlar arasında gezebilirsiniz. Bu sayede başkalarının metotlarını kullanırken ihtiyacımız olanı rahatlıkla seçebiliyoruz. Aşağıdaki örnekten Toplama metodunun 4 farklı versiyonu olduğunu görebiliriz.

Method Overload

Biraz daha gerçekçi bir senaryo ile örnek yapalım. Projeler içerisinde mail gönderimi sıkça kullanılan bir yapıdır. Mail gönderimi yaparken bir ya da birden daha fazla kişiye mail gönderebilirsiniz. Tek bir metot oluşturup birden fazla kullanıcıya gönderecek şekilde projeyi tasarlarsak, tek bir kişiye mail gönderirken de döngüye sokmamız gerekecek. Bunun yerine tek bir kişiye yollanacak maili direkt yollamak daha efektif bir çözüm olur. Şimdi bu senaryoyu örnekleştirelim.

static void Main(string[] args)
{
   SendMail("dunya@mail.com", "mars@mail.com", "Merhaba", "Merhaba Dünya!");
   string[] to = { "ali@mail.com", "ayse@mail.com", "bulent@mail.com" };
   SendMail(to, "derya@mail.com", "Toplantı", "Toplantı 1 saat sonraya ertelenmiştir");

   Console.ReadKey();
}
static void SendMail(string to, string from, string subject, string message)
{
    Console.WriteLine($"{to} kişisine {from} kişisinden {subject} konulu bir mail gönderildi");
}

static void SendMail(string[] to, string from, string subject, string message)
{
    foreach (string item in to)
    {
        Console.WriteLine($"{item} kişisine {from} kişisinden {subject} konulu bir mail gönderildi");
    }
}

Method Overloading Örneği

Recursive (Rekürsif – Öz Yinelemeli) Metodlar

Recursive metotlar kendi kendini çağırabilen metotlardır. Genellikle matematiksel işlemlerde tercih edilirler. Kendi içerisinde bir döngüye sahiptir. Örneğin Fibonacci sayıları için rekürsif bir metot yazalım.

static void Main(string[] args)
{
   Console.WriteLine("Kaçıncı Fibonacci sayısını görmek istiyorsunuz?");
   int order = int.Parse(Console.ReadLine());
   Console.WriteLine($"{order}. Fibonacci Sayısı: {Fibonacci(order)}");

   Console.ReadKey();
}

static int Fibonacci(int order)
{
    if (order == 1)
    {
        return 1;
    }
    else if (order == 2)
    {
        return 1;
    }
    else
    {
        return Fibonacci(order - 1) + Fibonacci(order - 2);
    }
}

Kullanıcıdan aldığımız fibonacci sayısı 3 olsun. order parametresi 3 olarak Fibonacci metoduna düştü. order 1 ve 2 değerinden farklı olduğu için else kısmına düştü ordan da Fibonacci(2) + Fibonacci(1) geri döndü. Bu noktada ilk hesaplamayı her zaman en sağdakinden yapar. Fibonacci(1) için tekrar metot çalışır. order = 1 olarak yeniden en baştan çalışmaya başladı. (Bu noktada order = 3 olan çağrımız yok olmuyor, bellekte tutulmaya devam ediyor.) order 1 değerine eşit olduğu için bellekte tuttuğumuz ilk adıma bir sonucu ile geri döndü. Bir sonraki adımda Fibonacci(1) işleminin sonucunu bellekte tutup Fibonacci(2) işlemini bulmaya gidiyor. order = 2 olarak metoda tekrar düşüyor. order 2 değerine eşit olduğu için bellekte tuttuğumuz ilk adıma bir sonucu ile geri döndü. Şimdi aslında en başta yaptığımız order = 3 çağrısına geri döndük ve bunun sonucu 1 + 1 yani 2 olarak main içerisine döndü.

Rekürsif Fibonacci

Anlaması biraz karışık görünse de bir kere debug mod üzerinde takip ederek çalıştırdığınızda çok rahat bir şekilde anlayacağınızı düşünüyorum. Recursive metotların Hello World’ü sayılan Faktöriyel hesaplamasını da yapalım.

static void Main(string[] args)
{
   Console.Write("Faktöriyelini hesaplamak istediğiniz sayıyı giriniz: ");
   int factorial = int.Parse(Console.ReadLine());
   Console.WriteLine($"{factorial}! = {Factorial(factorial)}");

   Console.ReadKey();
}

static int Factorial(int number)
{
    int result;

    if (number == 0)
    {
        result = 1;
    }
    else
    {
        result = number * Factorial(number - 1);
    }

    return result;
}

Rekürsif Faktöriyel

Benim bu yazıda ve metotlar konusunda bahsedeceklerim bu kadar. Metotlar kodlarımızın daha açıklayıcı ve kolay okunmasına yardımcı olur.  Clean code yazmak için mutlaka kod tekrarından kaçınmalıyız. Yani birden fazla yerde aynı işi kopyala yapıştır mantığı ile yapıyorsanız onu bir metoda dönüştürebilirsiniz. Bir sonraki yazıya kadar kendinize iyi bakın. 😊