Tag Archives: parametre

C# Delegate Tipine Genel Bir Bakış

Bir delegate tipi, özel parametre listesi ve geri dönüş tipi ile birlikte bir metoda referans bildirir. Böylece bir delegate tipi oluşturursak bir metoda referans içeren bir nesne yaratmış oluyoruz. Bu metod ise bu referans ile çağırılabilir konuma gelmiş oluyor.

Aşağıdaki örneğimiz bir Fonksiyon adında bir delegate tanımlar ve onu kullanır;

using System;

namespace ConsoleApplication1
{
 delegate double Fonksiyon (double d);

class Carpma
 {
 double carpim;

public Carpma(double carpim)
 {
 this.carpim = carpim;
 }

public double Carp(double x)
 {
 return x * carpim;
 }
 }

class Test
 {
 static double Kare(double x)
 {
 return x * x;
 }

static double[] Uygula(double[] a, Fonksiyon f)
 {
 double[] sonuc = new double[a.Length];

for (int i = 0; i < a.Length; i++)
 sonuc[i] = f(a[i]);

return sonuc;
 }

static void Main()
 {
 double[] a = { 0.0, 0.5, 1.0 };

double[] kareler = Uygula(a, Kare);

double[] sinusler = Uygula(a, Math.Sin);

Carpma c = new Carpma(2.0);
 double[] ciftler = Uygula(a, c.Carp);
 }
 }

}

Fonksiyon delegate tipinin örneği double tipinde argüman alan ve geriye double türünde bir veri döndüren herhangi bir metoda referans olabilir. Uygula() metodu verilen Fonksiyon’u double[] ile uygular ve geriye bir double[] sonucunu döndürür. Main metodu içerisinde, Uygula 3 farklı fonksiyonu double[] ile uygular.

Bir delegate, bir statik metoda (Kare ve Math.Sin metodları gibi) ya da bir metod örneğine (c.Carp metodu gibi)referans olabilir.

Bir metod örneğine referans olan bir delegate, belirli bir nesne örneğine de referans olabilir. Ve bu metod örneği delegate ile çağırıldığında, bu nesne this ile birlikte yönelir.

Bir delegate örneği yaratmak için, bir metodu delegate değişkenine atayabiliriz:

using System;

namespace ConsoleApplication1
{
 delegate int Donusturme (int x);

class Test
 {
 static int Kare (int x)
 {
 return x * x;
 }

static void Main()
 {
 Donusturme d = Kare; //Delegate örneği oluşturma
 int sonuc = d(3); //Delegate'i çağır
 Console.WriteLine(sonuc); //9
 }
 }

}

Görüldüğü üzere, bir delegate örneği çağırmak, tıpkı bir metod çağırımına benzer. (d(3) gibi) Bu arada;

 Donusturme d = Kare; 

satırı aşağıdaki kodun kısaltılmış halidir;

Donusturme d = new Donusturme (Kare);</p>  <p>d(3)

kodumuz da

d.Invoke(3)

kodunun kısaltılmışıdır.

Delegate Uyumlu Metodlar Yazmak

Bir delegate değişkeni metodlara dimanik olarak atanır. Bu delegate uyumlu metod yazmanın anahtarıdır. Aşağıdaki örnekte, Donustur adından bir yardımcı metodumuz var ve bir dizinin elemanlarını karelerine dönüştürür:

using System;

namespace ConsoleApplication1
{
 delegate int Donusturme (int x);

class Yardımcı
 {
 public static void Donustur(int[] degerler, Donusturme d)
 {
 for (int i = 0; i < degerler.Length; i++)
 degerler[i] = d(degerler[i]);
 }
 }

 class Test
 {
 static int Kare (int x)
 {
 return x * x;
 }

static void Main()
 {
 int[] degerler = { 1, 2, 3 };
 Yardımcı.Donustur(degerler, Kare);

foreach (int i in degerler)
 Console.Write(i + " "); // 1 4 9
 }
 }
}

Her delegate örneği çoklu kullanım yeteneğine sahiptir. Bunun anlamı; bir delegate örneği sadece tek bir metoda referans değil, birden fazla metoda referans olabilir. + ve += operatörleri delegate örneklerini birleştirir. Örneğin:

 DelegateOrnek d = MetodOrnek;</p>  <p>d += FarklıMetodOrnek; 

Bu durumda d delegate’ini çağırmak bu iki metodu çağırmak anlamına gelir. Eklenilme sırasına göre delegate tipleri çağırılır.

– ve –= operatörleri de sağıdaki metodu solundaki delegate örneğinden silerler. Örneğin;

 d –= MetodOrnek; 

Bu durumda d delegate’ini çağırmak sadece FarklıMetodOrnek metodunu çağırmamız anlamına gelir.

NOT: Delegate tipleri immutable tiplerdir. Bunun anlamı += veya –= operatörlerini kullandığınızda yeni bir delegate örneği oluşturmuş olursunuz ve varolan değişkeni ona atarsınız.

Çoklu gönderilen bir delegate tipi eğer void olmayan bir geri dönüş tipine sahipse, çağıran bu delegate’in içerdiği son metodun geri dönüş değerini alır. Önde bulunan metodlar yine de çağırılır fakat bunların geri dönüş değerleri görmezden gelinir.

NOT: Tüm delegate tipleri dolaylı olarak System.MulticastDelegate sınıfından türetilir. O da System.Delegate sınıfından kalıtım alır. += ve –= operatörlerini kullandığımızda aslında System.Delegate sınıfının statik tanımlanmış Combine ve Remove metodlarını kullanmış oluruz.

Statik Metod vs Metod Örneği

Bir delegate nesnesi bir metod örneğine atandığında, sadece atandığı metod olan referansı değil, ayrıca ait olduğu metodu da korur. System.Delegate sınıfının Target özelliği bu örneği gösterir. Örneğin;

using System;

namespace ConsoleApplication1
{
 public delegate void İlerleme (int TamamlanmaYuzdesi);

class Test
 {
 static void Main()
 {
 Ornek o = new Ornek();
 İlerleme i = o.İlerlemeOrnek;
 i(90); // 90
 Console.WriteLine(i.Target == o); // True
 Console.WriteLine(i.Method); // Void İlerlemeOrnek(Int32)
 }
 }

class Ornek
 {
 public void İlerlemeOrnek(int TamamlanmaYuzdesi)
 {
 Console.WriteLine(TamamlanmaYuzdesi);
 }
 }
}

Generic Delegate Tipleri

Bir delegate tipi generic tipinde parametreler içerebilir. Örneğin;

public delegate T Donusturme<T> (T arg);

Bu tanımlama ile, Donustur yardımcı metodunu herhangi bir tip ile kullanabiliriz:

 public class Yardımcı
 {
 public static void Donustur<T> (T[] degerler, Donusturme<T> t)
 {
 for (int i = 0; i < degerler.Length; i++)
 degerler[i] = t(degerler[i]);
 }
 } 

Func ve Action Delegate’leri

Generic delegate’ler ile, herhangi bir geri dönüş tipi ve herhangi sayıda argümanı olan metodlar için küçük delegate tipleri oluşturmak mümkündür. Bu delegate tiplerinden olan Func ve Action delegate’leri System namespace’i içerisinde yer alır.

 delegate Sonuc func<out Sonuc>();
 delegate Sonuc func<in T, out Sonuc>(T arg);

delegate void Action ();
 delegate void Action<in T>(T arg); 

Bir önceki örneğimizde Donustur delegate örneğini, sadece basit bir T argümanı alan ve aynı cinsten bir değer geri döndüren Func delegate’i ile yer değiştirebiliriz:

 public static void Donustur<T> (T[] degerler, Func <T, T> donus)
{
 for (int i = 0; i < degerler.Length; i++)
 degerler[i] = donus (degerler[i]);
} 

Delegate  Uyumluluğu

Tip

Tüm delegate tipleri, imzaları aynı olsa bile birbiri ile uyuşmayan tiplerdir.

 delegate void D1();
 delegate void D2();

D1 d1 = Metod1;
 D2 d2 = d1; // Derleme zamanı hatası 

Aşağıdaki örnek ise herhangi bir hata vermez:

 D2 d2 = new D2 (d1); 

Delegate örnekleri eğer aynı metodlara hedef gösterilirse bunlar eşit olarak nitelendirilir. Örneğin:

 delegate void D();
 ...
 D d1 = Metod1;
 D d2 = Metod1;
 Console.WriteLine (d1 == d2); // True döndürür. 

Birden fazla metoda referans gösteren delegate tipleri, eğer aynı metodlar aynı sırayla ise bu tipler de eşit olarak nitelendirir.

Parametre

Bir delegate tipi, hedef gösterdiği metodtan daha belirli parametre tiplerine sahip olabilir. Bu duruma contravariance adı verilir. Örneğin;

 using System;

namespace ConsoleApplication1
{
 delegate void StringEtki (string s);

class Test
 {
 static void Main()
 {
 StringEtki se = new StringEtki(NesneEtki);
 se("Merhaba");
 }
 static void NesneEtki(object o)
 {
 Console.WriteLine(o);
 }
 }
} 

Bu örneğimizde, StringEtki delegate tipi string tipindeki bir argüman ile çağırılır. Ve bu argüman hedef metoda aktarıldığında, bu argüman dolaylı olarak object tipine çevrilir (üst tipi olduğundan).

Geri Dönüş

Delegate’in geri dönüş tipi hedeflediği metodun geri dönüş tipinden daha az belirli olabilir. Bu duruma da covariance adı verilir. Örneğin;

 using System;

namespace ConsoleApplication1
{
 delegate object NesneGetir ();

class Test
 {
 static string StringGetir()
 {
 return "Merhaba";
 }
 static void Main()
 {
 NesneGetir o = new NesneGetir(StringGetir);
 object sonuc = o();
 Console.WriteLine(sonuc); //Merhaba
 }
 }
} 

NesneGetir geriye bir object döndürmesi bekler, fakat object alt sınıfı bunu ayrıca yapar. Delegate’in geri dönüş tipi “covariant”’tir.

C#’ta Method Parametreleri

Parametreler, bir değere atanan ya da bir methoda referans eden değişkenler için kullanılır. Method parametreleri, method çağırıldığında, asıl değerlerini argümanlardan alırlar. 4 çeşit parametre tipi bulunur; değer parametreleri (value parameters), referans parametreleri (reference parameters), çıktı parametreleri (output parameters) ve parametre dizileri (parameters arrays).

Değer parametreleri (value parameters), girdilerde parametre geçirilmelerinde kullanılır. Bir değer parametresi, methodlara geçirilen argümanların ilk değerlerini içeren yerel değişkenler ile uyuşur.

Değer parametrelerindeki değişiklikler, parametreye geçirilen argümanları etkilemezler.

Önemli: Değer parametrelerindeki değişiklik durumları, argümanları etkilemezler kelimeleri yanıltıcı olabilir çünkü türetilmiş sınıflar referans tip’te parametre içeriğini değiştirebilir. Parametre değerleri değiştiremez fakat referans edilmiş içerik (nesne) değiştirebilir.

Değer parametreleri seçimli olabilir, varsayılan değeri belirlemek için, böylece argümanlara ilişkilendirme yapılabilir.

Referans parametreleri, hem girdi (input) hem de çıktı (output) parametre geçişleri için kullanılır. Referans parametrelerini geçiş argümanları; bir değişken, yürütme süresinceki methodlar, ayrıca referans parametreleri argüman değişkenleri ile  aynı bellek bölgesini simgelerler. Bir referans parametresi ref anahtar sözcüğü ile gösterilir. Aşağıdaki örnek; ref anahtar sözcüğünün kullanımını gösterir.

class Test
{
static void Degistir(ref int x, ref int y)
{
int temp = x;
x =  y;
y = temp;
}
static void Main()
{
int i = 10, j = 20;
Degistir(ref i, ref j);
Console.WriteLine("{0} {1}", i, j); // Çıktı 20 10 olur.
}
}

Bu örneği ref anahtar sözcüksüz yazsaydık, çıktı “10 20” olurdu. Yani o değişkenlere referans vermeden bunların yerleri değiştirilemezdi.

Önemli: Referans parametreleri C# tarafından “referans ile geçiş” olarak adlandırılır; önce bir nesne örneğini bir methoda geçirir ve o method o nesne örneğine bir referans alır.

Referans parametreleri, çok küçük bir farkla “referans ile geçiş”’ten farklıdır. Bu durumda, referans bir değişkenin kendisindedir, bir nesne örneğinde değil. Eğer bu değişken, bir değer tipi içerirse bu tamamen normal bir durumdur. Bu değere “referans ile geçiş” yapılmamıştır değişken bu değeri tuttuğu sürece.

Referans parametrelerini düşünmenin iyi bir yolu, referans parametreleri, değişkenleri argümanlara geçiştirmek için bir takma ad’a dönüşürler şeklinde düşünmektir. Yukardaki örnekte, x ve i aslında aynı değişkendirler. Bellekte aynı bölgeye kaynak gösterirler.

Çıktı parametreleri (Output parameters), çıktı parametre geçişleri için kullanılırlar. Çıktı parametreleri referans parametrelerine benzerdir, fark olarak burada değeri ilk çağıran-sağlayan argüman önemsizdir. Çıktı parametreleri, out anahtar sözcüğü ile gösterilirler. Aşağıdaki örneği inceleyebiliriz;

using System;

namespace HelloWorld
{
class Test
{
static void Bolme(int x, int y, out int sonuc, out int kalan)
{
sonuc = x / y;
kalan = x % y;
}
static void Main()
{
int son, kal;
Bolme(20, 3, out son, out kal);
Console.WriteLine("{0} {1}", son, kal); // Çıktı 6 2 olur.
}
}
}

Önemli: CLR, direkt olarak sadece ref parametrelerini destekler. Out parametresi metadata içerisinde temsil edilir.

Parametre dizileri, bir methoda birden fazla sayıda argüman değişkeninin geçirilmesine izin verirler. Parametre dizileri, params anahtar sözcüğü ile gösterilirler. Bir methodun sadece son parametresi bir parametre dizisi olabilir ve parametre dizisinin tipi tek boyutlu dizi tipi olmak zorundadır. System.Console sınıfının Write ve Writeline methodları parametre dizilerinin kullanımına iyi bir örnektir. Bu iki method şu şekilde tanımlanmıştır:

 public class Console
{
public static void Write(string fmt, params object[] args) {...}
public static void WriteLine(string fmt, params object[] args) {...}
} 

Bu tür method tanımlamalarına, Visual Studio içerisinde methodun üzerine gelip sağ tıkladığımızda “Go to definition” sekmesiyle ulaşabiliriz. (Kısayol F12)

 

 

 

 

 

 

 

Parametre dizileri içeren bir method içerisinde, parametre dizileri bir dizi tipinin parametreleri ile tam olarak aynı şekilde davranır. Fakat, bir parametre dizisine sahip method, hem parametre dizisi tipindeki basit argümanı hem de parametre dizisindeki element tiplerinin argüman numaralarını geçirebilir. Sonrasında, bir dizi örneği otomatik yaratılır ve verilen argümanları ilişkilendirir. Örneğin;

 Console.WriteLine("x={0} y={1} z={2}", x, y, z); 

aşağıdaki koda eşdeğerdir

 string s = "x={0} y={1} z={2}";
object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine(s, args);

Önemli: Params anahtar sözcüğü kullanımının güzel bir tarafı da, opsiyonel bir kısayol oluşturmasıdır. Yani aşağıdaki şekilde bir kod yazmamızı önler:

 static object[] GetArgs() { ... }

static void Main()

{
object[ ] args = GetArgs();
object a = args[0];
object b = args[1];
object c = args[2];
Console.WriteLine("a={0} b={1} c={2}", a, b, c );
} 

Şimdi de bu methodu çağırıyorum ve compiler benim yerine bu parametreler için bir dizi oluşturuyor.

 static object[] GetArgs() { ... }

static void Main()

{
Console.WriteLine("a={0} b={1} c={2}", GetArgs() );
} 

Fakat bu günlerde, .NET içerisinde sadece çok az method bir dizi geriye döndürüyor. Bu nedenle bir çok yazılımcı, IEnumerable<T> kullanmayı esneklik amacıyla tercih ediyorlar. Bu nedenle ileride şöyle bir kod yazabilirsiniz;

 static IEnumerable<object> GetArgs { }

static void Main()
{
Console.WriteLine("x={0} y={1} z={2}", GetArgs().ToArray());
}