4 Temmuz 2013 Perşembe

Ref ve Out Anahtar Sözcüklerinin Kullanımı

C# dilinde temel olarak iki veri türü vardır. Bunlardan birincisi referans türleri ikincisi ise değer türleridir. Referans türleri bir ifade(metot çağrımı, atama ifadesi vs.) içinde kullanıldığı zaman nesnenin bellekteki adresi üzerinden işlem yapılır. Yani nesnenin bütün verisi ayrıca kopyalanmaz. Değer türlerinde ise durum daha farklıdır. Değer türleri yani int, byte,bool gibi veri türleri herhangi bir ifade içinde kullanılırsa değişkenin yeni bir kopyası çıkarılır ve işlemler bu yeni kopya üzerinden gerçekleştirilir. Dolayısıyla orjinal nesnenin değeri hiç bir şekilde değiştirilemez. Asıl konumuza geçmeden önce değişkenlerin referans yolu ile aktarımının ne demek olduğunu ve bu iki tür arasındaki farkı daha iyi anlamak için basit bir örnek vermekte fayda görüyorum.

Aşağıdaki programdaki gibi x ve y gibi iki int türünden değişkenimiz olsun. Bu değişkenlerin değerlerini değiştirmek(swap) için Degistir() isimli bir metot yazmak istediğimizi düşünelim. Bu metot ilk akla gelebilecek şekilde aşağıdaki gibi yazılır.

using System;
namespace ConsoleApplication2
{
class Class1
{
static void Main()
{
int x = 10;
int y = 20;
Degistir(x,y);
Console.WriteLine("X = " + x.ToString());
Console.WriteLine("Y = " + y.ToString());
}
static void Degistir(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
}
}

Yukarıdaki programı derleyip çalıştırdığımızda x ve y değişkenlerinin değerlerinin değiştirilmediğini ve aynı değerde kaldıklarını görürüz. Bunun sebebi Degistir() metoduna gelen x ve y değişkenleri ile Main() metodunda tanımlamış olduğumuz x ve y değişkenlerinden tamamen bağımsız yeni değişkenler olmasıdır. Dolayısıyla Degistir() metodunda yaptığımız değişiklikler orjinal değişkenlerimizi hiç bir şekilde etkilememiştir. Değişkenlerin bu şekilde aktarılmasına "değer yolu ile aktarma" yada "pass by value" denilmektedir.

Yukarıdaki programı aşağıdaki gibi biraz değiştirip x ve y değişkenleri bir sınıf aracılığıyla metota gönderirsek değiştirme işlemini yapabiliriz.

using System;
namespace ConsoleApplication2
{
class C
{
public int deger;
public C(int x)
{
deger = x;
}
}

class Class1
{
static void Main()
{
C x = new C(10);
C y = new C(20);
Degistir(x,y);
Console.WriteLine("X = " + x.deger.ToString());
Console.WriteLine("Y = " + y.deger.ToString());
}
static void Degistir(C x, C y)
{
int temp = x.deger;
x.deger = y.deger;
y.deger = temp;
}
}
}

Bu progrmı derleyip çalıştırdığımızda x ve y nesnelerinin deger üye elamanlarının yer değiştirildiğini göreceksiniz. Yer değiştirmeyi yapabilmemizin sebebi x ve y değişkenlerinin referans yolu ile metoda geçirilmesidir. Yani Degistir() metodundaki x ve y değişkenleri ile Main() metodundaki x ve y değişkenleri bellekteki aynı bölgeyi temsil etmektedirler. Dolayısıyla Degistir() metodunda yaptığımız bir değişiklik Main() metodundaki değişkene de yansıyacaktır. Nesnelerin metotlara bu şekilde aktarılmasına ise "referans yolu ile aktarım" yada "pass by reference" denilmektedir.

Bir referans tipi olan string türü ile ilgili önemli bir istisna vardır. string referans türü olmasına rağmen metotlara string türünden değişkenler geçirilirken değer tiplerinde olduğu gibi kopyalanarak geçirilirler. Yani int türünden bir değişken ile string türünden bir değişken metotlara değer yolu ile aktarılırlar. Bunu test etmek için birinci programdaki int türü yerine string türünü kullanabilirsiniz.
Ref Anahtar Sözcüğü
Yukarıda denildiği gibi değer tipleri(int, double, byte vs.) metotlara kopyalanarak geçirilirler yani değişkenin birebir yeni bir kopyası oluşturulur. Ancak bazı durumlarda değer tiplerini de referansları ile metotlara geçirmek isteyebiliriz. C ve C++ dillerinde değer tiplerini referans yolu ile geçirmek için göstericilerden faydalanır. Yani metotlara değişkenlerin adresleri geçirilir. C# ta bu işlemi yapmak için gösterici yerine yeni bir anahtar sözcük olan ref kullanılır. ref anahtar sözcüğü değer türlerinin metotlara referans yolu ile geçirilmesini sağlar. Referans türleri zaten referans yolu ile geçirildiği için bu türler için ref anahtar sözcüğünü kullanmak gereksizdir. Ancak kullanımı tamamen geçerli kılınmıştır.

ref sözcüğü metot çağrımında ve metot bildiriminde aynı anda kullanılmalıdır. Yani metot bildiriminde ref ile birlikte kullanılan bir değişken, metot çağrılırken ref ile çağrılmalıdır. Yukarıda yazdığımız birinci programı ref sözcüğünün kullanımı ile yeniden düzenlersek Degistir() metodunun istediğimiz şekilde çalışmasını sağlayabiliriz.

using System;
namespace ConsoleApplication2
{
class Class1
{
static void Main()
{
int x = 10;
int y = 20;
Degistir(ref x,ref y);
Console.WriteLine("X = " + x.ToString());
Console.WriteLine("Y = " + y.ToString());
}
static void Degistir(ref int x, ref int y)
{
int temp = x;
x = y;
y = temp;
}
}
}

Bu programı derleyip çalıştırdığımızda x ve y değişkenlerinin değerlerinin değiştirildiğini göreceksiniz. Çünkü Degistir() metoundaki x ve y değişkenleri ile Main() metodundaki x ve ye değişkenleri aynı bellek bölgesindeki değeri temsil etmektedirler. Birinde yapılan değişiklik diğerinide etkilemektedir. Yani ilk durumdan farklı olarak ortada iki değişken yoktur, tek bir değişken vardır.

Unutmamamız gereken nokta metot çağrımının da ref anahtar sözcüğü ile birlikte yapılması zorunluluğudur. Eğer Degistir() metodunu Degistir(ref x, ref y) yerine Degistir(x,y) şeklinde çağırmış olsaydık derleme aşamasında

Argument '1': cannot convert from 'int' to 'ref int'
Argument '2': cannot convert from 'int' to 'ref int'

hatalarını alırdık.

ref sözcüğünün kullanımı ile ilgili diğer bir önemli nokta ise ref ile kullanılacak değişkenlere mutlaka değer atanmış olma zorunluluğudur. Herhangi bir değer verilmemiş değişkeni ref ile de olsa kullanamayız. Kullandığımız takdirde ise derleme aşamasında "Use of unassigned local variable" hatasını alırız. Bu durum ref sözcüğünün bir kısıtı olarak düşünülebilir. Ancak birazdan göreceğimiz out sözcüğü ile bu kısıtı ortadan kaldırabileceğimizi göreceğiz.

Out Anahtar Sözcüğü
Out anahtar sözcüğünün kullanım amacı ref anahtar sözcüğünün kullanımı ile tamemen aynıdır. Yani out ile de değer tipleri referans yolu ile aktarılır. Aralarındaki tek fark out ile kullanılacak değişkenlere ilk değer verme zorunluluğunun olmamasıdır. Yani ref sözcüğünün kullanımındaki kısıt, out ile birlikte ortadan kaldırılmıştır. out anahtar sözcüğünü genellikle bir metottan birden fazla geri dönüş değeri bekliyorsak kullanırız.
Yukarıdaki Degistir() metodunu out ile kullanılabilecek şekilde değiştirdiğimizde x ve y değişkenlerine ilk değer verme zorunluluğumuz kalkacaktır.

ref ve out sözcüklerinin C# dilinde kullanılmasının küçük bir farkı olsada IL dilinde ref ve out aynı şekilde implemente edilmiştir. ILDASM aracı ile Degistir() metodunun hem ref hemde out versiyonlarının bildiriminin aşağıdaki gibi olduğunu görürüz.

.method private hidebysig static void Degistir(int32& x, int32& y) cil managed
{
.maxstack 2
.locals init (int32 V_0)
IL_0000: ldarg.0
IL_0001: ldind.i4
IL_0002: stloc.0
IL_0003: ldarg.0
IL_0004: ldarg.1
IL_0005: ldind.i4
IL_0006: stind.i4
IL_0007: ldarg.1
IL_0008: ldloc.0
IL_0009: stind.i4
IL_000a: ret
}

Bu durum ref ve out sözcüklerinin kullanımındaki farkın C# derleyicisi ile sınırlı olduğunu göstermektedir. Yani CLR ile ilgili bir fark değildir.

Yazıyı bitirmeden önce out anahtar sözcüğünün kullanımına bir örnek vermek istiyorum. İki sayıdan büyük olanına geri dönen bir metot yazmak istediğimizi düşünelim. Aynı zamanda da bu iki sayıdan birincisinin mi ikincisinin mi büyük olduğunuda bu metotla öğrenmek istiyoruz. Her metodun tek bir geri dönüş değeri olabileceğine göre klasik yöntemlerle bunu ideal(!) bir şekilde gerçekleştiremeyiz. Bunun için ilk değer verilmemiş yeni bir parametreyi daha Max() isimli metoda göndereceğiz. Bu parametre metot içinde değiştirilerek kendisini çağıran metoda iletilecektir.

using System;
namespace Out
{
class Class1
{
static void Main()
{
bool b;
int max = Max(9,2,out b);
Console.WriteLine(b);
}
static int Max(int x,int y, out bool b)
{
if(x > y)
b = true;
else
b = false;
return Math.Max(x,y);
}
}
}

Yukarıdaki işlemi ref anahtar sözcüğü ile de yapabilirdik ancak bir metodun içinde değeri belirlenecek bir değişkene ilk değer vermek gereksiz ve mantıksızdır. Dolayısıyla bir metodun birden fazla değer geri vermesini istediğimiz durumlarda out anahtar sözcüğünü kullanmamız daha okunabilir ve daha düzenli programcılık açısından önemlidir.


Hiç yorum yok:

Yorum Gönder