Tag Archives: ref

C#’ta Struct Yapısı

Sınıflar gibi, struct yapıları veri ve fonksiyon üyeleri içeren veri yapılarıdır. Sınıflardan farklı olarak, struct yapısı değer tipidir ve heap bölge tahsisi gerektirmez. Bir struct değişkeni, direkt olarak struct’ın verisini tutar. Oysa sınıf tipinde bir değişken, dinamik olarak ayrılmış nesneye referans tutar. Struct yapısı, kullanıcı tanımlı kalıtımı desteklemez ve tüm struct yapıları dolaylı olarak object tipinden kalıtım alır.

Önemli: Struct yapılarının heap bölge tahisisi gerektirmemesi, bu yapıların hiçbir zaman bunu yapmadıkları anlamına gelmez.

Bu yapılar özellikle küçük veri yapıları kullanımında kullanışlı olurlar. Karmaşık sayılar ve koordinat sisteminde noktalar struct yapılarına iyi örneklerdir. Bir yapıda sınıf yerine struct kullanımı, bellek tahsis etmede ve programınızın performansında büyük farklılıklar yaratabilir.

Örneğin; aşağıdaki program 100 nokta oluşturan bir programdır. Sınıf yapısı ile kullandığımızda 101 adet farklı nesne – 1 adet dizi için, 100 adet te dizi üyeleri için – oluşturulur:

using System;


class Nokta

{

    public int x, y;


    public Nokta(int x, int y)

    {

        this.x = x;

        this.y = y;

    }

}

class Test

{

    static void Main()

    {

        Nokta[] noktalar = new Nokta[100];

        for (int i = 0; i < 100; i++)

            noktalar[i] = new Nokta(i, i);

    }

}

struct-1

Alternatif olarak Nokta yapısını struct yapabiliriz.

struct Nokta

{

    public int x, y;


    public Nokta(int x, int y)

    {

        this.x = x;

        this.y = y;

    }

}

Burada sadece 1 nesne yaratılır – 1 adet dizi için – ve Nokta örnekleri dizi içerisinde saklanırlar.

struct-2

Önemli: Struct yapısının performans açısından sağladığı faydayı “Her zaman struct kullanın” şeklinde algılamak yanlış olur. Tabi ki bazı senaryolarda struct yapısına bellek ayırmak ve bellekten almak daha az zaman alır fakat her struct ataması bilindiği gibi değer kopyalamasıdır (value copy). Bu her zaman referans kopyalamasından daha fazla zaman alır.

Struct yapıcıları new operatörü ile çağrılırlar fakat bu demek değildir ki belirli bir bellek ayrılmıştır. Nesnenin veya referansının dinamik olarak bellek ayrımı yerine, bir struct yapıcısı basitçe struct değerinin kendisini döndürür (genellikle yığının geçici bölgesinde) ve bu değer gereli olduğunda kopyalanır.

Sınıflarda, iki farklı değişken aynı nesneye referans gösterebilir ve bu şekilde bir değer üzerindeki işlemler aynı nesneyi referans eden diğer değişkeni etkileyebilir. Struct yapılarında, her değişken kendi verisinin kopyasını tutar ve bir değişkenin diğerini etkilemesi imkansızdır. Örneğin aşağıdaki kodu inceleyelim;

Nokta a = new Nokta(10, 10);

Nokta b = a;

a.x = 20;

Console.WriteLine(b.x);

Eğer buradaki Nokta örneği bir sınıf (class) ise, sonucumuz 20 olur çünkü a ve b aynı nesneye referans gösterirler. Eğer Nokta örneği bir struct ise, sonucumuz 10 olur çünkü ikinci satırdaki atama işlemi değer olarak a’daki değerleri b’ye kopyalar ve bu kopyalama 3. satırdaki a.x atamasını etkilemez.

Bu örnekte struct kullanımının 2 noktasının farkına vardır. Birincisi, tüm struct yapısını kopyalamak bir nesne referansını kopyalamaktan daha az verimli. Böylece atama yapma ve değer parametresi geçirme struct yapılarında referans tiplerine göre daha maliyetli olabilir. İkincisi, ref ve out parametreleri dışında, struct yapısına referans oluşturma imkansızdır.

Bill Wagner: Eğer tüm durumlarda değer anlamı (value semantics) istemiyorsanız, sınıf kullanmalısınız. Sınıflar bazı durumlarda değer anlamı uygulayabilirler, (string gibi) ama default olarak referans anlamına uyarlar. Bu fark sizin tasarımınızda yığın vs hesap bellek ayrımına göre daha çok farklılık gösterir.

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());
}