C#’ta Method Aşırı Yüklemek

C#’ta method aşırı yüklenmesi, method imzaları farklı olan aynı isimdeki methodları aynı sınıf içerisinde kullanıma izin verir. Aşırı yükleme mekanizması, parametrelere uygun eşleşen methodu bulur ya da herhangi bir basit eşleşme olmazsa bir hata üretir. Aşağıdaki örnek, arışı yükleme mekanizmasının nasıl çalıştığına bir örnektir. Main() methodunun içinde hangi methodun çağrılacağını yorum olarak yazdım.

using System;
using System.Collections;
using System.Linq;
using System.Text;

namespace HelloWorld
{
class Test
{
static void A()
{
Console.WriteLine("A()");
}
static void A(object x)
{
Console.WriteLine("A(object x)");
}
static void A(int x)
{
Console.WriteLine("A(int x)");
}
static void A(double x)
{
Console.WriteLine("A(double x)");
}
static void A<T>(T x)
{
Console.WriteLine("A<T>(T x)");
}
static void A(double x, double y)
{
Console.WriteLine("A(double x, double y)");
}
static void Main()
{
A();          // Çağırılan F()
A(1);         // Çağırılan F(int)
A(1.0);       // Çağırılan F(double)
A("abc");     // Çağırılan F(object)
A((double)1); // Çağırılan F(double)
A((object)1); // Çağırılan F(object)
A<int>(1);    // Çağırılan F<T>(T)
A(1, 1);      // Çağırılan F(double, double)
}
}
}

Önemli: Method aşırı yükleme yanlış kullanımlara neden olabilir. Genel olarak bakarsak, method yüklemeyi sadece methodların hepsi anlamsal olarak aynı işi yaptığı zamanda kullanmalıyız. Bir çok yazılımcı, bu olayı “tek bir method farklı argümanlara sahip” olabilir şeklinde düşünür. Aslında, yerel değişkenin, parametrenin veya özelliğin tipini değiştirme, farklı yüklemelerin çağırılmasına neden olabilir. Yazılımcılar method aşırı yüklemesinin yan etkisini göremeyebilirler. Fakat kullanıcılar için bu tamamen farklı sonuçlar doğurabilir.

Brad Abrams, .NET framework’ü geliştirme aşamasının başlarında, String adında bir sınıfın içinde şu method yüklemelerin problem yarattığını söylüyor:

public class String
{
public int IndexOf (string value);
// Örnekteki değerin index'ini geri döndürür.
public int IndexOf (char value);
// Örnekteki değerin index'ini geri döndürür.
public int IndexOf (char[] value);
// Değerin içindeki karakterlerin ilk index'ini geri döndürür.
} 

Buradaki son method, farklı sonuçlara yol açtığını söylüyor. Şöyle ki;

 "Joshua, Hannah, Joseph".IndexOf("Hannah"); // Geriye 7 döndürür. 

Fakat

"Joshua, Hannah, Joseph".IndexOf(new char [] {'H','a','n','n','a,'h;");
 // Geriye 3 döndürür.

Buradaki son methodun isminin method aşırı yüklemesine bağlı kalınarak aşağıdaki şekilde olması gerektiğini söyler:

public int IndexOfAny (char [] value);

C#’ta Virtual, Override ve Abstract Methodlar

C#’ta bir method örneği, virtual anahtar sözcüğü ile tanımlanırsa, o methoda virtual (sanal) method adı verilir.

Bir sanal method, ana sınıf tipinden ziyade, güncel atanmış tip ile ilişkilendirilmiş methodu kullanmanıza izin verir.

Bir sanal method türetilmiş sınıfı tarafından etkisiz hale getirilebilir. Bir method örneğinde override anahtar sözcüğü varsa, o method, kalıtım alınmış sanal methodu aynı method imzası ile geçersiz kılar. Bu tür bir sanal method eğer overload olmuşsa, tanımlanmış değişkenin data tipinden ziyade verinin gerçek sınıf tipi runtime zamanında kullanılır. Bunun anlamı; temel sınıf bir çok türetilmiş sınıf tarafından kullanılabilir.

Önemli: Buradaki ince bir nokta, override edilmiş bir sanal method, hala o sınıfın bir methodu olarak hesab katılır. Türetilmiş sınıf, sanal sınıf override edildiği zaman onu göstermek zorundadır. Bu override anahtar sözcüğünü yeni method oluşumunda kullanılarak gösterilir.

Bir abstract (soyut) method, uygulanmayan bir sanal methodtur. Bu methodlar, abstract anahtar sözcüğü ile tanımlanır ve sadece astract olarak tanımlanmış sınıflara izin verilir. Bir soyut sınıf, soyut olmayan türetilmiş tüm sınıflar tarafından override edilmelidir.

Aşağıdaki örnekte; soyut sınıf olarak tanımlanmış Deyim sınıfı, ki bir deyim ifade ağacı düğümü temsil eder, 3 tane de türetilmiş sınıf, Sabit, DegiskenReferansi ve İslem, ki bunlar da sabitlerin, değişken referanslarının ve aritmetik işlemlerin deyim ifade ağaçlarını uygularlar.


using System;
using System.Collections;
using System.Linq;
using System.Text;

namespace HelloWorld
{
public abstract class Deyim
{
public abstract double Hesapla(Hashtable vars);
}
public class Sabit : Deyim
{
double value;
public Sabit(double value)
{
this.value = value;
}
public override double Hesapla(Hashtable vars)
{
return value;
}
}
public class DegiskenReferansi : Deyim
{
string name;
public DegiskenReferansi(string name)
{
this.name = name;
}
public override double Hesapla(Hashtable vars)
{
object value = vars[name];
if (value == null)
{
throw new Exception("Bilinmeyen deşiken " + name);
}
return Convert.ToDouble(value);
}
}
public class İslem : Deyim
{
Deyim sol;
char op;
Deyim sag;

public İslem(Deyim sol, char op, Deyim sag)
{
this.sol = sol;
this.op = op;
this.sag = sag;
}

public override double Hesapla(Hashtable vars)
{
double x = sol.Hesapla(vars);
double y = sag.Hesapla(vars);
switch (op)
{
case '+': return x + y;
case '-': return x - y;
case '*': return x * y;
case '/': return x / y;
}
throw new Exception("Bilinmeyen operatör");
}
}
} 

Bu 4 sınıf, modern matematik işlemler için kullanılabilir. Örneğin, bu sınıf örneklerini kullanarak x + 7 deyimi şu şekilde gösterilebilir;


static void Main()
{
Deyim d = new İslem(new DegiskenReferansi("x"), '+', new Sabit(7));
} 

Burada Deyim sınıfının Hesapla methodu verilen değeri hesaplamak ve bir dobule değeri üretmek görevindedir. Bu method Hashtable adında değişken isimlerini ve değerlerini içeren bir parametre alır. Hesapla methodu, sanal soyut bir method olduğundan, bunun anlamı; soyut olmayan türetilmiş sınıflar güncel bir uygulama sağlamak için bu methodu override etmek zorundalar.

Sabit sınıfının Hesapla implementasyonu, basit olarak geriye depolanmış bir sabit (constant) döndürür. DegiskenReferansi implementasyonu, Hashtable içerisindeki değişken ismine bakar ve sonuçlanan değeri geri döndürür. İslem implementasyonun görevi, öncelikle sol ve sağ operandlarını (Recursif olarak çağırılan Hesapla() methodları ile) hesaplayarak, verilen aritmetik işlemi gerçekleştirmektir.

Aşağıdaki program Deyim sınıfını kullanarak x * (y + 3) işlemini farklı değerler kullanarak hesaplar;


static void Main()
{
Deyim d = new İslem(new DegiskenReferansi("x"), '*', new İslem(new DegiskenReferansi("y"), '+', new Sabit(3)));

Hashtable vars = new Hashtable();

vars["x"] = 2;
vars["y"] = 5;
Console.WriteLine(d.Hesapla(vars)); // Çıktı 16 olur.

vars["x"] = 3;
vars["y"] = 7;
Console.WriteLine(d.Hesapla(vars)); // Çıktı 30 olur.
} 

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

C#’ta Methodlar

C#’ta methodlar, bir sınıf ya da nesne tarafından gerçekleştirilen hesaplamaları ya da eylemleri uygulayan üyelerdir. Static methodlara sınıflar aracılığıyla erişilebilirken, Instance (örnek) methodlara bir sınıfın örneği tarafından erişilebilir.

Methodlar bir parametre listesine (boş olabilen) sahip olabilir ki bunlar methoda geçirilen değer ya da değişkenlere referans gösterirler. Bununla birlikte bir geri dönüş tipine sahip olabilirler (return type) ki bu da method içinde hesaplanan değerlerin programa geri vereceği değeri belirler. Eğer bir methodun geri döüş tipi “void” ise, o method bir değer döndürmez.

Tıpkı tipler gibi, methodlar da, bir takım tip parametre setlerine sahip olabilirler. Bunun amacı method çağırıldığında, hangi tiplerin belirlenmesi gerektiğidir.

Bir method imzası (signature), sınıf içerisinde eşsiz bir değer olmalıdır. Bir method imzası; var olan methodun ismi, tip parametrelerin sayısı, erişim düzenleyicileri (modifier) ve parametrelerin tiplerinden oluşur. Method imzası geri dönüş tipi içermez.

Önemli: Generic tiplerin şanssız bir sonucu olarak, yapısal bir tip (constructed type), özdeş imzalar içeren iki farklı methoda sahip olabilir. Örneğin;


class Sınıf<T>
{
void Method(T t){}
void Method(int t){}
}

şeklide bir kod hiçir hata içermez. Fakat Sınıf<int>’in özdeş imzalara sahip iki farklı methodu olmuş olur. Daha sonra göreceğimiz gibi, bu durum method’ların aşırı yüklenmesinde ve arayüz implementasyonlarında bazı ilginç senaryolara yol açar. İyi bir yönlendirme olarak; bunu yapmayın.

C#’ta Alanlar (Fields)

C#’ta alanlar, bir sınıf ya da bir sınıf örneği ile ilişkilendirilmiş değişkenlerdir. Static değiştiricisi ile tanımlanmış alanlar static field olarak tanımlanır. Static field’lar, tam olarak bir bellek yeri tanımlarlar. Kaç tane sınıf örneği oluşturulursa oluşturulsun, static field’ın sadece bir tane kopyası vardır.

Önemli: Static field’lar her generic type için ayrı bir yapısal yapıdır. Örneğin elinizde;

class Stack<T>
{
public readonly static Stack<T> bos = tralalalala;
}

varsa, Stack<int>.bos ve Stack<string>.bos alanları farklı alanlardır.

Static değiştiricisiz tanımlanmış alanlar birer örnek alanlardır. Her bir sınıf örneği, o sınıfın her bir örnek alanın kopyasını içerir.

Aşağıdaki örnekte; her bir Color sınıfının örneği, her bir r (red), g (green), b (blue) alanlarına sahiptir ama sadece bir adet kopya Siyah, Beyaz, Kırmızı, Yeşil ve Mavi static (durağan) alanlar mevcuttur.

public class Color
{

public static readonly Color Siyah = new Color(0, 0, 0);
public static readonly Color Beyaz = new Color(255, 255, 255);
public static readonly Color Kırmızı = new Color(255, 0, 0);
public static readonly Color Yeşil = new Color(0, 255, 0);
public static readonly Color Mavi = new Color(0, 0, 255);

private byte r, g, b;
public Color(byte r, byte g, byte b)
{
this.r = r;
this.g = g;
this.b = b;
}
}

Yukarıda görüldüğü gibi sadece okunabilir alanlar readonly değiştiricisi ile tanımlanmıştır. Bir readonly alana atama, sadece o alanın tanımlamasındaki bir kısmından ya da aynı sınıftaki bir yapıcı (constructor) sayesinde gerçekleşebilir.

Önemli: Readonly kelimesi dışarıdaki tip yapıcısı tarafından o alanın konumunun değiştirilmesini önler fakat o konumdaki değeri korumaz. Örneğin, aşağıdaki gibi İsimler şeklinde bir sınıfımız olsun;

public class İsimler
{
public static readonly StringBuilder İlkDogan = new StringBuilder("İlker");
public static readonly StringBuilder İkinciDogan = new StringBuilder("Soner");
}

Bir yapıcının dışından direkt olarak İlkdoğan örneğinin sonucunu değiştirmek bize bir derleyici hatası verecektir.

İsimler.İlkDogan = new StringBuilder("Caner"); // Derleyici Hatası

Ama, StringBuilder örneğini modifiye ederek aynı sonuca aşağıdaki şekilde ulaşabilirim;

İsimler.İlkDogan.Remove(0,6).Append("Caner");

Console.WriteLine(İsimler.İlkDogan); // Output “Caner” olur.

Bu yüzden, read-only kullanımı değişmez tipler için sınırlı kullanılmalıdır şeklinde tavsiye edilir. Değişmez tipler (Immutable types), int, double, string gibi açıkça setter’lara (belirleyiciler) sahip değildirler.

C# Sınıflar Kavramı

Sınıflar, C# tiplerinden en temelidir. Bir sınıf, alanları (fields) ve aksiyonları (metod ve diğer fonksiyon üyelerini) basit bir birim içerisinde birleştiren veri yapılarıdır. Sınıflar, dinamik olarak oluşturulmuş örnekler için tanımlama sağlarlar. Ki bunlardan nesneler olarak bilinir. Sınıflar kalıtımı (inheritance) ve polymorphism’i destekleyen yapılardır.

Yeni sınıflar, kullanılan sınıf bildirimlerinden yaratılırlar. Bir sınıf tanımlaması, öncelikle sınıfın niteliği (modifiers), sınıfın ismi, ana sınıf (eğer varsa) ve sınıfa uygulananan arayüzlerler (interface) sıralaması ile olur. Bu tanımlamalardan sonra { ve } işaretleri ile sınıf tanımlaması tamamlanır.

Aşağıda Nokta adında basit bir sınıf tanımlaması yapılmıştır:

public class Nokta
{
public int x, y;
public Nokta(int x, int y)
{
this.x = x;
this.y = y;
}
}

New operatörü ile oluşturulmuş örnek sınıf ki yeni örnek için bellek ayrılmış, örneğe ilk kullanım için yapıcı (constructor) çağrımı yapılır ve geriye bu örneğe bir referans döndürür.

Aşağıda iki Nokta nesnesi oluşturulmuş ve bu iki nesneye refefans iki değişken içinde saklanır:

Nokta n1 = new Nokta(0, 0);
Nokta n2 = new Nokta(10, 20);

Memory bir nesnenin kullanımı ortadan kalktığında o nesnenin kullandığı alanı geri çağırır. Bir nesnenin C#’ta açıkça serbest bırakılması hem gereksiz hem de imkansızdır.

C# İfade Kavramı

C#’ta ifadeler operand ve operatör adı verilen iki elementten oluşur. Bir ifadenin operatörleri, operandlara hangi operasyonların uygulanacağını gösterir. Operatör’lere örnek olarak;  +, -, *,/, ve new anahtar kelimesi gösterilebilir. Operand’lara örnek olarak ise; alanlar (fields), yerel değişkenler ve ifadeler gösterilebilir.

Bir ifade birden fazla operatör içeriyorsa, hangi operatörün işlenileceğine o operatörün öncelik sırası karar verir. Örneğin; X + Y * Z ifadesi X + (Y * Z) olarak işlenir * (çarpma) operatörü + (toplama) operatörüne göre daha önceliklidir.

Önemli: Öncelik sırası hangi operatörlerin hangi sıra ile işleneceğine karar verir. Fakat hangi operandın önce işleneceğine karar vermez. Operand’lar soldan sağa işlenir periyodik olarak. Yukarıdaki örnekte, önce X hesaplanıldı, sonra Y, sonra Z, sonra çarpma işlemi yerine getirildi, sonra da toplama işlemi. X’in Y’den önce hesaplanmasının nedeni solda olması, çarpma işleminin toplama işleminden önce gerçekleşmesinin nedeni önceliğinin yüksek olmasıdır.

Bir çok operatör aşırı yüklenebilir (Overloading). Operatör yüklemesi, kullanıcı tanımlı operatör işlemelerinde spesifik işlemler yapılmasına olanak tanır.

Aşağıdaki tablo, C# operatörlerini yüksek öncelikten düşük önceliğe göre listeler. Aynı kategorideki operatörler eşit önceliğe sahiptir.

Kategori İfade Açıklama
Birincil x.m Üye erişimi
Birincil x(…) Metod ve delegate tanımlama
Birincil x[…] Dizi ve indexer erişimi
Birincil x++ Öncelikli arttırma
Birincil x– Öncelikli azaltma
Birincil new T(…) Nesne ve delegate oluşturma
Birincil new T(…){…} Kullanıma hazır nesne oluşturma
Birincil new {…} Anonymus nesne kullanımı
Birincil new T[…] Dizi oluşturma
Birincil typeof(T) T için mevcut System.Type tipi
Birincil checked(x) İşaretlenmiş konuda hesaplanan ifade
Birincil unchecked(x) İşaretlenmemiş konuda hesaplanan ifade
Birincil default(T) T tipinin mevcut varsayılan değeri
Birincil delegate {…} Anonymus fonksiyon (Anonymus metod)
Tekil +x Özdeşlik
Tekil -x Değil
Tekil !x Lojik değil (Not)
Tekil ~x Bit bit değil (~x = –x-1)
Tekil ++x Ön arttırma
Tekil –x Ön azaltma
Tekil (T)x X’i açık olarak T tipinde çevirme
Çoğulsal x * y Çarpma
Çoğulsal x / y Bölme
Çoğulsal x % y Mod (kalan)
Eklemeli x + y Ekleme, string birleştirme, delegate birleşimi
Eklemeli x – y Çıkarma, delegate uzaklaştırma
Öteleme x << y Sola öteleme
Öteleme x >> y Sağa öteleme
İlişkisel ve tip testi x < y Daha küçük
İlişkisel ve tip testi x > y Daha büyük
İlişkisel ve tip testi x <= y Küçük veya eşit
İlişkisel ve tip testi x >= y Büyük veya eşit
İlişkisel ve tip testi x is T Eğer x, bir T ise true döner, değilse false
İlişkisel ve tip testi x as T Eğer x, bir T tipi ise true döner, değilse false
Eşitlik x == y Eşittir
Eşitlik x != y Eşit değil
Lojik AND x & y Bit bit lojik olarak AND işlemi
Lojik XOR x ^ y Bit bit lojik olarak XOR işlemi
Lojik OR x | y Bit bit lojik olarak OR işlemi
Koşullu AND x && y Eğer x true ise y’yi hesaplar
Koşullu OR x || y Eğer x false ise y’yi hesaplar
Null Kaynaşma X ?? y Eğer x null ise y’yi hesapla, değilse x’i hesapla
Koşullu x ? y : z Eğer x true ise y’yi hesapla, x false ise z’yi hesapla
Atama ve anonymous fonksiyonlar x = y Atama
Atama ve anonymous fonksiyonlar x op= y Bileşik atama. Destekleyen operatörler: *= /= %= += -= <<= >>= &= ^= |=
Atama ve anonymous fonksiyonlar (T x) => y Anonymus fonksiyonlar (lambda expression)

C# Attribute (Nitelik) Kavramı

Bir C# programında assembly’ler, tipler, üyeler, geri dönüş değerleri, parametreler ve diğer varlıklar onların davranışlarını belirleyecek değiştiriciler desteklerler. Örneğin, bir metoda erişebilirlik durumunu public, protedted, private ve internal anahtar kelimeleri belirler. C# bunlar gibi kullanıcı tanımlı bilgileri runtime zamanında programa uygulamak için genelleştirebilir yapıya sahiptir. Program bu gibi ilave bildirim bilgilerini attribute tanımlayarak belirler.

Nitelikler bir sınıf üyesi değildir, sadece ilişkilendirildikleri üyeler için ilave bilgi sağlarlar. Aşağıdaki örnek bir YardımAttribute niteliği tanımlar. Bu nitelik programdaki üyelerin ilişkilerine bir link sağlar:

using System;

namespace ConsoleApplication1
{
public class YardımAttribute : Attribute
{
string url;
string konu;

public YardımAttribute(string url)
{
this.url = url;
}

public string Url
{
get { return url; }
}

public string Konu
{
get { return konu; }
set { konu = value; }
}
}
}

Tüm attribute sınıfları .NET Framework tarafından desteklenen System.Attribute sınıfından türetilirler. Nitelikler ilişkilendirildikleri tanımlamdan önce köşeli parantezler içerisinde nitelik ismi ve herhangi bir argümanı ile tanımlanırlar. Eğer bir niteliğin adı Attribute ile bitiyorsa, o kısmı yazılmadan o niteliğe referans gösterilebilir. Yukarıdaki örneğimiz için konuşursak şu şekilde olur;

 [Yardım("<a href="http://sonergonul.net">http://sonergonul.net")]</a>
public class Oge
{
[Yardım("<a href="http://sonergonul.net&quot;">http://sonergonul.net"</a>, Konu="Göster")]
public void Göster(string text) { }
}

Bu örnek YardımAttribute niteliğini Oge sınıfına ve başka bir YardımAttribute niteliğini de sınıfın içerisindeki Göster metoduna bağlar. Attribute sınıfının public yapıcısı, nitelik bir program varlığına bağlandığında sağlanması gereken bilgiyi kontrol eder. İlave bilgiler attribute sınıfının read-write özelliklerine referans ile sağlanır (Az önceki Konu özelliği gibi).

Aşağıdaki örnek’e bakacak olursa, yansıma kullanılarak runtime zamanında programın varlığının nitelik bilgilerine nasıl erişebileceğimizi gösterir:

class Test
{
static void YardımGöster(MemberInfo uye)
{
YardımAttribute y = Attribute.GetCustomAttribute(uye, typeof(YardımAttribute))
as YardımAttribute;

if (y == null)
{
Console.WriteLine("{0} için yardım yok", uye);
}
else
{
Console.WriteLine("{0} için yardım", uye);
Console.WriteLine(" Url={0} Konu={1}", y.Url, y.Konu);
}
}

static void Main()
{
YardımGöster(typeof(Oge));
YardımGöster(typeof(Oge).GetMethod("Göster"));
}
}

Burada GetCustomAttribute() metodunu okumak istediğimiz bir niteliğin adını bildiğimiz zamanlarda kullanırız. Ve sonra da bir niteliğie ait bir referans elde ettiğimizde o niteliğin üyelerine ulaşabiliriz. Eğer okumak istediğimiz niteliğin adını bilmiyorsak GetCustomAttributes() metodunu kullanabiliriz. Bu metod, bir nesneye bağlanmış tüm niteliklerin listesini okur. Ve aşağıdaki şekilde bu metodu kullanabilirdik;

object[] att = typeof(Oge).GetCustomAttributes(false);
foreach(object o in att)
{

Console.WriteLine(o);

}

Bir nitelik yansıma (Reflection) ile çağırıldığı zaman, attribute sınıfının yapıcısı programın sağladığı bilgiyle çağırılır ve sonuç niteliği geri döndürülür. Eğer ilave bilgiler özellikler ile sağlandıysa, bu özellikler nitelik örneği döndürülmeden önce verilen değere atanırlar.

Attribute parametreleri 2 kategoriye ayrılırlar: konumsal ve isimsel. Konumsal parametreler nitelik sınıfının yapıcısında parametre olarak bulunurlar (public YardımAttribute(string url), http://sonergonul.net). İsimsel parametreler de attribute tipinin public alan ya da özelliklerinde bulunurlar (public string Konu, Konu=”Göster” gibi). Bir nitelik belirlediğinizde, o nitelik ilişkilendirdiği temel alınan nitelik yapıcısının konumsal parametrelerini içermek zorundadır. İsimsel parametreler ise opsiyoneldir. İhtiyaca göre kullanılır ya da kullanılmazlar. Ve bunların sırası önemli değildir. İsimsel parametrelerde değer atanması gerekli değildir. Varsayılan değerleri kullanılabilir.

Bir sınıf varlığına birden fazla nitelik bağlayabiliriz. Her nitelik tek bir köşeli parantez içinde virgül ile ayrılabilir ya da hepsi kendi köşeli parantezleri içerisinde yazılabilir. Aşağıdaki 3 örnek aynı görevi görür;

   [Serializable, Obsolete, Obfuscation]
public class Sınıf { }

[Serializable, Obsolete]
[Obfuscation]
public class Sınıf { }

[Serializable]
[Obsolete]
[Obfuscation]
public class Sınıf { }

Standart Nitelikler (AttributeUsage, Conditional, Obsolete)

Eğer bir nitelik sınıfınızda, tanımlanan niteliğin hangi öğe tipler için uygulanabileceğini belirtmek istersek AttributeUsage kullanmamız gerekir. Örneğin yazımızın başındaki YardımAttribute sınıfından önce;

[AttributeUsage(AttributeTargets.Method)]

şeklinde bir tanımlama yapsaydık, bu nitelik sadece metod yapılarına bağlanabilirdi. AttributeTargets.All seçeneği ile bu niteliğin tüm yapılara bağlanabildiğini gösterebiliriz. Ya da sadece enum ve sınıf tiplerine bağlanabileceğini belirtmek için aşağıdaki şekilde kullanabiliriz;

AttributeTargets.Enum | AttributeTargets.Class

Conditional niteliği ise koşullu metodlar oluşturulmasını sağlar. Koşullu metod ancak #define ile tanımlandığında çağırılır. Aksi halde bu metod geçilir. Bu nitelik System.Diagnostics namespace’inin içerisinde bulunur. Aşağıdaki kodunumuza bir göz atalım;

#define SARI

using System;
using System.Diagnostics;

class Fenerbahce
{
[Conditional("SARI")]
void Sarı()
{ Console.WriteLine("Sarı Rengi"); }

[Conditional("LACİVERT")]
void Lacivert()
{ Console.WriteLine("Lacivert Rengi"); }

public static void Main()
{
Fenerbahce t = new Fenerbahce();

t.Sarı();
t.Lacivert();
}
}

Bu kodumuzun çıktısı sadece “Sarı Rengi” olur. Yukarıdaki gibi Lacivert() metodunda herhangi bir #define tanımı yapılmadığı için bu metod çalıştırılmayacaktır. Aslında bu kadar basittir bu niteliğin kullanımı. Bu nitelik sadece metodlar üzerinde uygulanabilir. Koşullu metodlarla ilgili kısıtlamalardan birisi de bu metodların her zaman void döndürmeleri gerektiğidir.

Obsolete niteliği ise bir program öğesini kullanılmayan olarak işaretlememize olanak verir. Aşağıdaki örneğimizi inceleyelim:

using System;

class Test
{
[Obsolete("Bölme2 metodunu kullan")]
static int Bolme(int x, int y)
{
return x / y;
}

static int Bolme2(int x, int y)
{
return y == 0 ? 0 : x / y;
}

public static void Main()
{
Console.WriteLine(Test.Bolme(5, 4));

Console.WriteLine(Test.Bolme2(5, 4));
}
}

Görüldüğü üzere Bolme2 metodu ile Bolme metodunun daha gelişmiş bir versiyonunu kullandık. Bu programı çalıştırdığımızda iki adet Writeline ifadesi de çalışır ve sonuçlarını 1 gösterir. Fakat kod penceresine baktığımızda Test.Bolme üzerinde aşağıdaki şekilde bir uyarı alırız:

[deprecated] int Test.Bolme(int x, int y)

Warning: ‘Test.Bolme(int x, int y)’ is obsolete: ‘Bolme2 metodunu kullan’