Skip to content

Bölüm 8 / 15

Metotlar

Parametre, dönüş değeri, overload, out/ref, params.

10 dakikalık okuma

Programların büyümesini yönetilebilir kılan araç: aynı işi yapan satırlara ad verip tek yerden çağırmak. Bu derste metot tanımlamayı, parametre geçiş kurallarını — değer tipinin kopyalanıp referans tipinin paylaşıldığı o kritik ayrımı — out/ref'i, overload'u ve params'ı kuracaksın. Dersin sonunda iki dersin borcu kapanıyor: int.TryParse deseni ve guard clause.

01Ne öğreneceksin

  • Metot imzası: dönüş tipi, ad, parametre listesi; void ve return
  • Parametre geçişi: değer tipi kopyalanır, referans tipinin adresi kopyalanır — ve bu farkın doğurduğu hatalar
  • out, ref, varsayılan parametre, isimli argüman, params; TryParse deseni
  • Overload kuralları ve iyi metot pratiği: tek iş, fiil ad, guard clause

02Neden metot: tekrarın adı olsun

Not ortalaması hesaplayan beş satırın programında üç yerde geçtiğini düşün. Kural değişse ("en düşük not atılsın") üç yeri de bulup düzeltmen gerekir; birini kaçırırsan program üç yerde iki farklı kural işletir — en sinsi hata türlerinden biri. Metot, bu satırlara ad verir: iş tek yerde tanımlanır, her yerden adıyla çağrılır, değişiklik tek noktadan yayılır. Bu ilkenin sektördeki adı DRY'dır (Don't Repeat Yourself — kendini tekrarlama); Hat 04'te tasarım ilkesi olarak derinleşecek, temeli bu derstir.

İkinci kazanç adlandırmadır: OrtalamaHesapla(notlar) çağrısını okuyan kişi, beş satır hesabı okumadan ne olduğunu bilir. İyi bölünmüş bir program, üstten okununca ne yaptığını cümle gibi anlatan çağrılardan oluşur.

03Tanım ve çağrı: imzanın anatomisi

int Topla(int a, int b)        // dönüş tipi + ad + parametreler = imza
{
    return a + b;              // return: değeri çağırana teslim et ve çık
}

var sonuc = Topla(3, 5);       // çağrı: argümanlar 3 ve 5, dönüş 8
Console.WriteLine(Topla(sonuc, 10));   // dönüş değeri ifade olarak da kullanılır

Parçaları adlandır: dönüş tipi metodun üreteceği değerin tipi; parametreler metodun dışarıdan istediği girdilerin tipli listesi; çağrıda verilen gerçek değerlere argüman denir. return iki iş yapar: değeri teslim eder ve metodu o anda bitirir — return'den sonraki satırlar çalışmaz. Değer üretmeyen metot dönüş tipi olarak void yazar ve çıplak return; ile erken çıkabilir:

void SelamVer(string ad)
{
    Console.WriteLine($"Merhaba {ad}");
}

Metot adları C# geleneğinde PascalCase yazılır (her kelime büyük harfle: OrtalamaHesapla) ve fiille başlar — metot iş yapar, adı işi söylemeli. Değişkenlerin camelCase'iyle karıştırma; iki gelenek yan yana yaşar. Metotları dosyanda üst düzey kodunun altına yazabilirsin; dosya düzeninin tam kuralları sınıflarla birlikte (10. ders) oturacak.

04Parametre geçişi: kopya mı, paylaşım mı

Bu bölüm dersin kalbi. Kural tektir ve 6. dersten tanıdıksın: argüman, metoda kopyalanarak geçer. Soru şu: kopyalanan ne?

Değer tiplerinde (int, double, bool…) kopyalanan değerin kendisidir; metodun parametresi bağımsız bir kopyadır, üzerindeki değişiklik dışarıya yansımaz:

void Artir(int sayi)
{
    sayi = sayi + 1;           // yalnızca kopya değişti
}

var x = 5;
Artir(x);
Console.WriteLine(x);          // 5 — dışarıdaki x'e hiçbir şey olmadı

Referans tiplerinde (dizi, List, Dictionary…) kopyalanan adrestir; metot, çağıranla aynı veriye bakar. İçeriği değiştirirsen çağıran da görür:

void IlkiniSifirla(int[] dizi)
{
    dizi[0] = 0;               // paylaşılan verinin içeriği değişti
}

var notlar = new[] { 85, 92 };
IlkiniSifirla(notlar);
Console.WriteLine(notlar[0]);  // 0 — değişiklik dışarıda görünür

Bu davranış hem güçtür hem tuzaktır. Güç: büyük bir List'i metoda vermek kopyalama maliyeti taşımaz — yalnızca adres geçer. Tuzak: bir metodun, parametre aldığı koleksiyonu sessizce değiştirmesi, çağıranın haberi olmayan yan etkidir; kod inceleme kültüründe böyle metotlardan ya kaçınılır ya da adıyla ilan edilir (Sirala değil YerindeSirala). İnce ayrıntıyı da bil: metot içinde parametreye yeni bir dizi atarsan (dizi = new[] {...}) dışarısı etkilenmez — değişen yalnızca kopyalanan adresin kendisidir; paylaşılan veri değişmedikçe çağıran hiçbir şey görmez.

05out ve ref: kopya kuralını bilerek delmek

Bazen değer tipinin metot içinde değişip dışarıya yansımasını istersin. İki anahtar kelime, parametreyi kopya yerine dışarıdaki değişkenin kendisine bağlar:

void Ikile(ref int sayi)       // ref: var olan değeri al, yerinde değiştir
{
    sayi *= 2;
}
var a = 5;
Ikile(ref a);                  // çağrıda da ref yazılır — niyet iki tarafta görünür
Console.WriteLine(a);          // 10

bool YasHesapla(string girdi, out int yas)   // out: sonucu bu değişkene doldur
{
    if (int.TryParse(girdi, out var dogumYili) && dogumYili > 1900)
    {
        yas = 2026 - dogumYili;
        return true;
    }
    yas = 0;                   // out parametresi HER yolda doldurulmak zorunda
    return false;
}

Fark net: ref "değeri olan bir değişken ver, okuyup değiştireceğim" der — değişken dolu gelmek zorundadır. out "boş değişken ver, dolduracağım" der — metot her çıkış yolunda onu doldurmak zorundadır, doldurmayan kod derlenmez. Günlük hayatta ref'i nadiren yazacaksın; out'u ise sürekli Try deseninde kullanacaksın:

Console.Write("Doğum yılın: ");
if (int.TryParse(Console.ReadLine(), out var yil))
{
    Console.WriteLine($"Yaş: {2026 - yil}");
}
else
{
    Console.WriteLine("Geçersiz sayı girdin.");
}

2. dersin borcu burada kapanıyor: int.Parse geçersiz girdide programı düşürüyordu; TryParse bool döner, sayıyı out ile verir, hata fırlatmaz. Kullanıcı girdisi her zaman TryParse ile çevrilir — Parse yalnızca girdinin geçerliliğinden emin olduğun iç veride kullanılır. Bu, üretim koduyla alıştırma kodunu ayıran ilk reflekstir; geçen ders Dictionary'de gördüğün TryGetValue ile aynı ailedendir.

06Varsayılan parametre, isimli argüman, params

void Logla(string mesaj, string seviye = "BILGI")   // varsayılanlı parametre
{
    Console.WriteLine($"[{seviye}] {mesaj}");
}
Logla("Kayıt eklendi");                    // [BILGI] Kayıt eklendi
Logla("Disk dolu", "HATA");                // [HATA] Disk dolu
Logla("Bakım başlıyor", seviye: "UYARI");  // isimli argüman: hangi parametreye gittiği açık

Varsayılanlı parametreler listenin sonunda durmak zorundadır. İsimli argüman, art arda aynı tipten parametre alan çağrılarda okunabilirliği kurtarır: Aktar(kaynak: aHesabi, hedef: bHesabi) — parametre sırasını ezberlemek yerine adıyla söylersin; art arda bool alan çağrılarda (Kaydet(true, false) gibi okunmaz kod) isimli argüman pratikte zorunludur.

params, son parametreyi "istediğin kadar argüman" yapar; metot içinde dizi olarak görünür:

int Toplam(params int[] sayilar)
{
    var toplam = 0;
    foreach (var s in sayilar) { toplam += s; }
    return toplam;
}
Console.WriteLine(Toplam(1, 2));           // 3
Console.WriteLine(Toplam(5, 10, 15, 20));  // 50 — çağıran diziyle uğraşmadı

Tanıdık geldi mi: string.Join(", ", dizi), Console.WriteLine'ın format halleri — baştan beri params'lı metotlar çağırıyordun. Tek satırlık metotlar için ok gövdesi (expression body) yazımını da tanı; modern C# kodunda çok göreceksin: int Kare(int x) => x * x;

07Overload: aynı ad, farklı imza

Aynı işi farklı girdilerle yapan metotlar aynı adı taşıyabilir; buna overload (aşırı yükleme) denir. Derleyici hangi metodu çağıracağını argümanların tipine ve sayısına bakarak derleme anında seçer:

double Ortalama(int a, int b) => (a + b) / 2.0;
double Ortalama(int a, int b, int c) => (a + b + c) / 3.0;
double Ortalama(int[] sayilar)
{
    var toplam = 0;
    foreach (var s in sayilar) { toplam += s; }
    return (double)toplam / sayilar.Length;
}

Ortalama(3, 5);              // 1. metot
Ortalama(3, 5, 8);           // 2. metot
Ortalama(new[] { 3, 5, 8 }); // 3. metot — üçü de aynı adla, karışıklık yok

Kural ve klasik sınav sorusu: overload'u ayıran şey parametre listesidir (tip, sayı, sıra). Yalnızca dönüş tipi farklı olan iki metot overload değildir, derleme hatasıdır — çağrı yerinde Hesapla(3) yazıldığında derleyici hangisini seçeceğini bilemezdi. Console.WriteLine'ın int'i de string'i de double'ı da basabilmesi overload'dur — baştan beri kullanıyorsun.

08İyi metot pratiği: tek iş ve guard clause

Metot yazmayı öğrenmek sözdiziminden ibaret değil; ölçütleri de bugünden kur:

  • Tek iş: metot adındaki fiili yapar, fazlasını yapmaz. OrtalamaHesaplaVeYazdir adındaki "Ve", metodun bölünmesi gerektiğinin işaretidir — hesaplayan metot değeri döndürür, yazdırmak çağıranın kararıdır.
  • Kısalık: ekrana sığmayan metot bölünme adayıdır. Katı satır kuralı yoktur; pratik ölçüt, metodun ne yaptığını tek cümleyle söyleyebilmendir.
  • Guard clause: 4. derste sözü verilen kalıp. Geçersiz durumları metodun başında ele alıp hemen çık; asıl iş, if merdivenine gömülmeden düz akar:
// İç içe: asıl iş iki kademe içeride           // Guard clause: geçersizler kapıda elenir
double Ortalama(int[] notlar)                    double Ortalama(int[] notlar)
{                                                {
    if (notlar != null)                              if (notlar == null) { return 0; }
    {                                                if (notlar.Length == 0) { return 0; }
        if (notlar.Length > 0)
        {                                            var toplam = 0;
            // asıl hesap burada...                  foreach (var n in notlar) { toplam += n; }
        }                                            return (double)toplam / notlar.Length;
    }                                            }
    return 0;
}

Sağdaki sürümün okunuşu düzdür: "şunlar geçersiz, ele; kalan asıl iştir." Geçersiz girdiye 0 döndürmek her zaman doğru cevap değildir — bazen hata fırlatmak gerekir; o kararın araçları Hat 03'te (cs-exception). Bugünün kazanımı biçim: erken çık, düz akış bırak.

09Mini alıştırma — küçük hesap kütüphanesi

Yaklaşık 20 dakika · dotnet new console -o metotlar

  1. Üç overload'lu Ortalama ailesini kendin yaz (iki int, üç int, int dizisi). Dizili sürüme guard clause ekle: null ya da boş dizide 0 dön. Üçünü de çağırıp doğrula; sonra yalnızca dönüş tipi farklı dördüncü bir Ortalama(int a, int b) eklemeyi dene ve derleme hatasını oku.
  2. bool TryNotHarfi(string girdi, out string harf) yaz: girdi TryParse ile sayıya çevrilemiyorsa ya da 0-100 aralığı dışındaysa false dön ve harfi "?" yap; geçerliyse 4. dersteki harf tablosuyla doldur, true dön. Kullanıcıdan aldığın değerle çağır; "abc", "-5" ve "85" girdilerinin üçünü de dene — program hiçbirinde çökmemeli.
  3. int Toplam(params int[] sayilar) ve void Yazdir(string baslik, params int[] sayilar) yaz; Yazdir, başlığı ve sayıları string.Join ile tek satırda bassın.
  4. Yan etki deneyi: void Sifirla(int[] d) içinde d'nin ilk elemanını 0 yap, dışarıdaki dizide değişikliği gör. Sonra metodun içinde d = new int[] { 9, 9 }; yazıp dışarıdaki dizinin ETKİLENMEDİĞİNİ gör; iki durumun farkını 04. bölümün diliyle bir cümleyle açıkla.
  5. Kapanış: 1. adımdaki Ortalama'yı bilerek toplam / notlar.Length (cast'siz) yaz, 85-90-92 için 89.0 yerine 89 çıktığını gör — tamsayı bölmesi tuzağının metot içinde de seni beklediğini unutma, cast'i geri koy.

10Kendini sına

Bir metoda int geçmek ile int[] geçmek arasında dışarıya etki bakımından ne fark vardır?

İkisinde de argüman kopyalanır; fark kopyalananın ne olduğudur. int değer tipidir — değerin kendisi kopyalanır, metottaki değişiklik dışarı yansımaz. int[] referans tipidir — adres kopyalanır, metot çağıranla aynı diziye bakar: eleman değişikliği dışarıda görünür. Ancak parametreye yeni dizi atamak (d = new ...) yalnızca kopya adresi değiştirir, dışarıyı etkilemez.

out ile ref arasındaki fark nedir? TryParse neden out kullanır?

ref, değeri olan bir değişkeni metodun okuyup yerinde değiştirmesi içindir — dolu gelmek zorundadır. out, metodun doldurması için verilen boş değişkendir — metot her çıkış yolunda ona atama yapmak zorundadır; ikisinde de anahtar kelime hem imzada hem çağrıda yazılır. TryParse'ın iki çıktısı vardır: başarı durumu (bool, dönüş değeri) ve çevrilen sayı — ikincisini out ile teslim eder; böylece hata fırlatmadan güvenli çevrim yapılır.

Overload'u geçerli kılan nedir? Yalnızca dönüş tipi farklı iki metot neden derlenmez?

Aynı adlı metotlar parametre listeleriyle (tip, sayı, sıra) ayrışıyorsa overload geçerlidir ve seçim derleme anında argümanlara göre yapılır. Yalnızca dönüş tipi farklıysa çağrı yerinde (Hesapla(3)) derleyicinin hangisini seçeceğini belirleyecek hiçbir bilgi yoktur — bu yüzden dönüş tipi imza ayrımına katılmaz ve böyle iki metot derleme hatasıdır.

Guard clause nedir, iç içe if'e göre kazancı nedir?

Metodun başında geçersiz durumları tek tek ele alıp erken çıkmaktır (return). Kazanç okunabilirlik: asıl iş, koşul merdivenine gömülmek yerine metodun gövdesinde düz ve girintisiz akar; her guard satırı "şu durum geçersizdir" diye tek başına okunur. Ayrıca yeni geçersiz durum eklemek, iç içe yapıyı derinleştirmek yerine başa bir satır eklemekten ibaret olur.

11Sınav köşesi

Yazılıda nasıl sorulur

En verimli kalıp çıktı sorusudur ve neredeyse hep 04. bölümü ölçer: metoda int geçilip değiştirilen kodda dışarıdaki değerin değişmediğini, dizi geçilende değiştiğini bilmek soruyu tek başına çözer; ref'li sürüm de üçüncü varyant olarak gelir. İkinci kalıp kural bilgisi: "Aşağıdakilerden hangisi geçerli bir overload değildir?" (yalnızca dönüş tipi farklı olan), "out parametresi için hangisi doğrudur?" (metot içinde atanmak zorundadır). return'ün metodu anında bitirdiği — return'den sonraki satırın ölü kod olduğu — da doğru-yanlış maddesi olarak sorulur.

Sözlüde nasıl anlatılır — 2 dakikalık taslak

"C#'ta parametre geçişini anlatır mısınız?" mülakatın ayırt edici sorusudur — yüzeysel bilen burada dökülür. Akış: (1) Varsayılan geçiş kopyadır; değer tipinde değerin, referans tipinde adresin kopyası — dizinin içeriği metottan değişebilir ama değişkene yeni nesne atamak dışarıyı etkilemez. (2) Kopya kuralını bilerek delen araçlar ref ve out; farklarını söyle, out'u TryParse ile örnekle — "kullanıcı girdisini her zaman TryParse ile alırım" cümlesi üretim refleksi gösterir. (3) Overload'un derleme anında, parametre listesine göre çözüldüğünü ekle. (4) Kapanış: "Metotlarımı tek işe indirger, geçersiz durumları guard clause'la başta elerim" — tasarım olgunluğu tek cümlede.