|  | 
        
            | 
                    
                        | C#'ın Gelecekteki Özellikleri |  |  
                        | 
	
    
		
            | Gönderiliyor lütfen bekleyin... | 
 |  | 
        
            | Bildiğiniz gibi C# dili 2001 yılında Microsoft tarafından çıkarılan ve nesne 
  yönelimli programlama tekniğine %100 destek veren bir programlama dilidir. C#, 
  programcılara sunulduğundan beri bir çok programcının dikkatini çekmiştir. Bu 
  ilgide en önemli neden herhalde C# dilinin kendinden önce çıkarılmış olan JAVA 
  ve C++ dillerini örnek almasıdır. Evet C# modern çağın gerektirdiği bütün yazılım 
  bileşenlerini içermekle beraber eski programlama dillerinde bulunan iyi özellikleri 
  de yapısında barındırmaktadır. Microsoft ve C# dil tasarımcıları her geçen gün 
  yeni piyasa araştırmaları yaparak dile katabilecekleri özellikleri tartışmaktadırlar. 
  Bu amaçla C# dilinin tasarımcıları yakın bir zaman içinde C# diline eklemeyi 
  düşündükleri yeni özellikleri bildirmişlerdir. Bu yazıda muhtemelen "VS.NET 
  for Yukon(VS.NET Everett'ten sonraki versiyon)" ile birlikte uygulamaya 
  konulacak C# dilinin muhtemel özelliklerini özetlemeye çalışacağım. Bu bildirinin 
  tamamını C# topluluğunun resmi sitesi olan www.csharp.net 
  adresinden okuyabilirsiniz.
 
 C# diline yakın bir zamanda eklenilmesi düşünülen özellikler 4 ana başlık altında 
  toplanmıştır. Bu özellikler temel olarak aşağıdaki gibidir.
 
 1 - Generics (Soysal Türler)
 
 2 - Iterators
 
 3 - Anonymous Methods (İsimsiz-Anonim- Metotlar)
 
 4 - Partial Types (Kısmi Türler)
 
 Bu yazıda yukarıda başlıklar halinde verilen her bir konuyu ayrıntılı olarak 
  inceleyip, programcıya ne gibi faydalar sağlayabileceğini ve programların performansına 
  nasıl etki edeceğine değineceğim.
 
 1 - Generics
 
 Profesyonel programlamada, türden bağımsız algoritma geliştirme önemli bir tekniktir. 
  Türden bağımsız algoritmalar geliştirici için büyük kolaylıklar sağlamaktadır. 
  Söz gelimi iki int türden sayının toplanmasının sağlayan bir fonksiyonu yazdıktan 
  sonra aynı işlemi iki double türden sayı için tekrarlamak zaman kaybına sebep 
  olacaktır. C++ dilinde türden bağımsız algoritma kurabilmek için şablon(template) 
  fonksiyonları ve şablon sınıfları kullanılmaktadır. C#, türden bağımsız algoritma 
  geliştirmeye doğrudan destek vermiyor olsada dolaylı yollardan türden bağımsız 
  işlemler yapabilmek mümkündür. Bu işlemler C#'ta "Her şey bir Object'tir" 
  cümlesinin altında yatan gerçekle halledilmektedir. C#'ta herşeyin bir nesne 
  olması ve her nesnenin ortak bir atasının olması ve bu atanın da Object sınıfı 
  olması bu cümlenin altında yatan gerçektir. Dolayısıyla herhangi bir türe ait 
  referansı Object referasnlarına ataybiliriz. Yani bir bakıma türden bağımsız 
  bir işlem gerçekleştirmiş oluyoruz. Söze gelimi Object türünden bir parametre 
  alan bir fonksiyonu dilediğimiz bir nesne referansı geçebiliriz. Temel(base) 
  sınıfa ait referanslara türeyen(inherited) sınıf referanslarını ataybilmek nesne 
  yönelimli programlama tekniğinin sunduğu bir imkandır.
 
 C#'ta Object referanslarına istenilen türden referanslar atanabilir. Bu, büyük 
  bir imkan gibi görünsede aslında bazı dezavantajlarıda beraberinde getiriyor. 
  Çünkü çalışma zamanında Object türüne atanmış referanslar orjinal türe tekrar 
  geri dönüştürülmektedir. Kısaca unboxing olarak bilinen bu işlem özellikle değer(value) 
  ve referans(reference) türleri arasında yapıldığında önemsenecek büyüklükte 
  bir performans kaybı meydana gelmektedir. Çünkü değer ve referans türleri belleğin 
  farklı bölgelerinde saklanmaktadır. Bu durum boxing ve unboxing işlemlerinin 
  çalışma zamanında farklı bellek bölgeleri arasında uzun sürebilecek veri transferlerine 
  sebep olur. Bu tür bir performans kaybını bazı veri yapıları için önlemek için 
  C# dil tasarımcıları Generics isimli bi kavramın dile eklenmesini öngörmüşlerdir. 
  Bu sayede bazı veri yapılarında özellikle .NET sınıf küyüphanesindeki System.Collections 
  isim alanında bulunan veri yapılarında epeyce performans kazancı elde edilecektir.
 
 İsterseniz basit 
  bir yığın(stack) sınıfı üzerinden "generics" kavramının sağlayacağı 
  yaraları ve boxing/unboxing işlemlerinin etkisini inceleyelim.
 
 .NET sınıf kütüphanesinde de bulunan Stack sınıfı içinde her türden veri bulunduran 
  ve istenildiğinde bu verilere LIFO(son giren ilk çıkar) algoritmasına göre veri 
  çekilebilen bir veri yapısdır. .NET'teki Stack sınıfı ile bütün veri türlerine 
  ait işlemleri yapabilmek için Stack sınıfındaki veri yapısı Object olarak seçilmiştir. 
  Eğer bu böyle olmasaydı Stack sınıfının her bir tür için ayrı ayrı yazılması 
  gerekecekti. Bu mümkün olsa bile herşey bitmiş olmayacaktı. Çünkü Stack sınıfı 
  kullanıcını tanımlayacağı türleri barındıracak duruma gelmez. İşte bütün bu 
  sebeplerden dolayı Stack veri yapısında saklanan veriler Object olarak seçilmiştir. 
  Buna göre Stack sınıfının arayüzü aşağıdaki gibidir.
 
 
 
 
   
   
    | class 
      Stack {
 private int current_index;
 private object[] elemanlar 
      = new object[100]; 
           public 
        void Push(object veri)
 {
 .
 .
 elemanlar[current_index] 
        = veri;
 .
 .
 }
 
 public object Pop()
 {
 .
 .
 return 
        elemanlar[current_index];
 .
 .
 }
 }
 
 
 |  Bildirilen bu Stack sınıfının elemanı Object türünden olduğu için Push() metodu 
ile istediğimiz türden veriyi saklayabiliriz. Aynı şekilde Pop() metodu ile bir 
veri çekileceği zaman veri Object türünden olacaktır. Pop() metodu ile elde edilen 
verinin gerçek türü belli olmadığı için tür dönüştürme operatörü kullanılır. Örneğin,
 
 
 şeklinde yığına eklenen veriyi tekrar elde etmek için
 
 
 biçiminde bir tür dönüşümü yapmamız gerekir. Bu işlemler kendi tanımlayacağımız 
özel sınıflar içinde geçerlidir. Ancak int ve double gibi temel veri türlerindeki 
performans kaybı daha fazladır. Çünkü Push(3) şeklindeki bir çağrımda boxing işlemi 
gerçekleşirken Pop() metodunun çağrılmasında unboxing işlemi gerçekleşir. Üstelik 
bu durumda Pop() metodunun geri dönüş değerini byte türüne dönüştürmeye çalışırsak 
derleme zamanında herhangi bir hata almayız. Bu da çalışma zamanında haberimiz 
olmadan bazı veri kayıplarının olabileceğini gösterir. Kullanıcı tanımlı sınıflar 
ilgili bir yığın kullanıyorsak Pop() metodunun geri dönüş değerini farklı bir 
kullanıcı tanımlı sınıfa çaviriyorsak bu sefer de derleme zamanında hata alınmaz, 
ancak çalışma zamanında "invalid cast operation" istisnai durumu meydana 
gelir.
 
 Bütün eksi durumlardan kurtulmak için generics(soysal tür)'lerden faydalanılabilir. 
Soysal türler C++ dilindeki şablon sınıflarının bildirimi ile benzerdir. Bu tür 
sınıf bildirimlerine parametreli tip de denilmektedir. Parametreli tipler 
aşağıdaki gibi bildirilir.
 
 
 
   
   
    | class 
      Stack {
 private int current_index;
 private Veri türü[] 
      elemanlar; 
           public 
        void Push(Veri türü veri)
 {
 .
 .
 elemanlar[current_index] 
        = veri;
 .
 .
 }
 
 public Veri türü 
        Pop()
 {
 .
 .
 return 
        elemanlar[current_index];
 .
 .
 }
 }
 
 
 |  ile stack sınıfnın hangi türden verileri tutacağı stack nesnesini oluşturacak 
programcıya bırakılmıştır. Örneğin int türden verileri saklayacak bir yığın aşağıdaki 
gibi oluşturulur.
 
 
 
   
   
    | Stack> 
      yıgın = new Stack>; |  Yukarıdaki şekilde bir yıgın oluştrulduğunda Stack sınıfınuın bildirimindeki Veri 
türü ifadeleri int türü olarak ele alınacaktır. Dolayısıyla Pop() metodu ile 
yığından bir eleman çıkarılıp aşağıdaki gibi başka bir değişkene atanmak istendiğinde 
tür dönüştürme operatörünü kullanmaya gerek yoktur. Bu da boxing ve unboxing işlemlerinin 
gerçekleşmediği anlamına gelir ki istediğimiz de buydu zaten.
 
 
 
   
   
    | Stack> 
      yıgın = new Stack>; 
 yıgın.Push(3); // Boxing işlemi gerçekleşmez.
 
 int a = yıgın.Pop(); //Unboxing işlemi gerçekleşmez.
 |  Aynı şekilde yığınımızın double türden verileri saklamasını istiyorsak int yerine 
double kullanmalıyız. Bu durumda çalışma zamanında hem int hem de double verileri 
tutan yığın sınıfları oluşturulacaktır. Biz tek bir yığın sınfı bildirmiş olmamıza 
rağmen çalışma zamanı bizim için ayrı iki yığın sınıfı oluşturur.
 
 Soysal türleri kendi tanımladığımız sınıflar içinde oluşturabiliriz. Örneğin Musteri 
isimli bir sınıfın verilerini yığında tutmak için yığın sınıfını aşağıdaki gibi 
oluşturmalıyız.
 
 
 Bu durumda yığına sadece Musteri nesneleri eklenebilir. Yani yıgın.Push(3) şeklindeki 
  bir kullanım derleme aşamasında hata verecektir. Aynı zamanda yığından çekilecek 
  veriler de Musteri türündendir. Dolayısıyla tür dönüşümü uygun türler arasında 
  olmalıdır.
 
 Yığın sınıfı yukarıda anlatılan şekilde kullanıldığında yığındaki elemanların 
  belirli bir türden olduğu garanti altına alınır. Böylece Musteri türünden nesneleri 
  tutan bir yığına "3" gibi bir sayıyı ekleyemeyeceğimiz için daha gerçekçi 
  programlar yazılır.
 
 Stack örneğinde sadece bri tane parametre türü kullandık. Soysal türlerde istenilen 
  sayıda parametreli tür kullanılabilir. Örneğin Hashtable sınıfnındaki Deger 
  ve Anahtar ikilisi aşağıdaki gibi parametreli tür olarak bildirilebilir.
 
 
 
   
  Yani bir Hashtable 
  nesnesi oluşturulacağı zaman her iki parametre türü de belirtilmelidir. Örneğin 
  Anahtar türü int olan ve değer türü Musteri sınıfı olan bir Hashtable nesnesi 
  aşağıdaki gibi oluşturulabilir. 
    | public 
      class 
      Hashtable {
 public void Add(AnahtarTuru 
      anahtar, DegerTuru deger)
 {
 .....
 }
 
 public DegerTuru 
      this[AnahtarTuru anahtar]
 {
 .....
 }
 }
 |  
 
 
 
   
  Not : Parametre 
  sayısını aralarına virgül koyarak dilediğimiz kadar artırabiliriz. 
    | Hashtable,Musteri> 
      hashtable = new Hashtable,Musteri>; |  
 Soysal türlerin saydığımız avantajlarının yanında bu haliyle bazı dezavantajları 
  ve kısıtlamalarıda vardır. Söz gelimi Hashtable sınıfının bildirimi içinde AnahtarTuru 
  verisinin bazı elemanlarını bir ifade de kullanmak istiyoruz; derleyici hangi 
  AnahtarTuru parametrelei türünün hangi türden olduğunu bilmediği için bu durumda 
  sadece Object sınıfının ait metotlar ve özellikler kullanılabilir. Mesela Hashtable 
  sınıfının Add metodu içinde anahtar parametresi ile CompareTo() metodunu kullanmak 
  istiyorsak CompareTo metodunun bildirildiği IComparable arayüzünü kullanarak 
  aşağıdaki gibi tür dönüşümü yapmalıyız.
 
 
 
 
   
  Hashtable sınıfının 
  Add() metodu yularıdaki şekilde bildirilse bile hala eksik noktalar var. Mesela 
  AnahtarTuru parametresi eğer gerçekten IComparable arayüzünü uygulamıyorsa switch 
  ifadesi içinde yapılan tür dönüşümü geçersiz olacaktır ve çalışma zamanında 
  hata oluşacaktır. Çalışma zamanında meydana gelebilecek bu tür hataları önlemek 
  için yapılabilecek tek şey AnahtarTuuru parametresinin IComparable arayüzünü 
  uyguluyor olmasını zorlamaktır. Bu işlemi yapmak için AnahtarTuru parametresine 
  çeşitli kısıtlar(constraints) getirilir. Aşağıdaki Hashtable sınıfında 
  AnahtarTuru parametresinin IComparable arayüzünü uygulaması gerektiği söylenmektedir. 
  Bu kısıt için where anahtar sözcüğü kullanılır. 
    | public 
      class 
      Hashtable {
 public void Add(AnahtarTuru 
      anahtar, DegerTuru deger)
 {
 switch(((IComparable)anahtar).CompareTo(x))
 {
 
 }
 }
 }
 |  
 
 
 
 
   
  Dikkat ettiyseniz 
  uygulanan kısıttan sonra switch ifadesi içinde anahtar değişkeni üzerinde tür 
  dönüşümü işlemi yapmaya gerek kalmamıştır. Üstelik kaynak kodun herhangi bir 
  noktasında Hashtable nesnesini IComparable arayüzünü uygulamayan bir AnahtarTuru 
  parametresi ile oluşturursak bu sefer ki hata derleme zamanında oluşacaktır. 
    | public 
      class 
      Hashtable<AnahtarTuru, DegerTuru> where 
      AnahtarTuru : IComparable {
 public void Add(AnahtarTuru 
      anahtar, DegerTuru deger)
 {
 switch(anahtar.CompareTo(x))
 {
 
 }
 }
 }
 |  
 Not : parametreli türler üzerindeki kısıt sadece arayüz olmak zorunda değildir. 
  Arayüz yerine sınıflar da kısıt olarak kullanılabilir.
 
 Bir parametreli türe birden fazla arayüz kısıtı konabileceği gibi aynı sınıftaki 
  diğer parametreleri türler için de kısıt konulabilir. Ancak bir parametreli 
  tür için ancak sadece bir tane sınıf kısıt olabilir. Örneğin aşağıdaki Hashtable 
  sınıfında DegerTuru Musteri sınıfından tremiş olması gerekirken, AnahtarTuru 
  hem IComparable hemde IEnumerable arayüzünü uygulamış olması gerekir.
 
 
 
 
   
   
    | public 
      class 
      Hashtable<AnahtarTuru, DegerTuru> where AnahtarTuru : IComparable
 AnahtarTuru : IEnumerable
 DegerTuru     : Musteri
 {
 public void Add(AnahtarTuru 
      anahtar, DegerTuru deger)
 {
 switch(anahtar.CompareTo(x))
 {
 
 }
 }
 }
 |  
 2 - Iterators
 Bir dizinin elemanları 
  üzerinde tek tek dolaşma işlemine iterasyon denilmektedir. Koleksiyon tabanlı 
  nesnelerin elemanları arasında tek yönlü dolaşmayı sağlayan foreach döngü yapısının 
  bizim tanımlayacağımız sınıflar için de kullanılabilmesi için sınıfımızın bazı 
  arayüzleri uyguluyor olması gerekir. foreach döngüsü derleme işlemi sırasında 
  while döngüsüne dönüştürülür. Bu dönüştürme işlemi için IEnumerator arayüzündeki 
  metotlardan ve özelliklerden faydalanılmaktadır. Bu dönüştürme işleminin nasıl 
  yapıldığına bakacak olursak :
 
 
 
 
   
  foreach döngüs 
  yapısı için gerekli olan arayüzlerin uygulanması özellikle ağaç yapısı şeklindeki 
  veri türleri için oldukça zordur. Bu yüzden C# sınıfların foreach yapısı ile 
  nasıl kullanılacağına karar vermek için yeni bir yapı kullanacaktır. 
    | ArrayList 
        alist = new ArrayList(); 
 foreach(object 
        o in alist)
 {
 BiseylerYap(o);
 }
 
 // Yukarıdaki foreach bloğunun karşılığı aşağıdaki gibidir.
 
 Enumerator e = alist.GetEnumerator();
 
 while(e.MoveNext())
 {
 object 
        o = e.Current
 BiseylerYap(o);
 }
 
 
 |  
 Sınıflarda, foreach anahtar kelimesi bir metot ismi gibi kullanılarak sınıfın 
  foreach döngüsünde nasıl davranacağını bildirebilriz. Her bir iterasyon sonucu 
  geri döndürülecek değeri ise yield anahtar sözcüğü ile belirtilir. Örneğin 
  her bir iterasyonda farklı bir tamsayı değeri elde etmek için sınıf bildirimi 
  aşağıdaki gibi yapılabilir.
 
 
 
 
   
  Yukarıda bildirilen 
  Sınıf türünden nesneler üzerinde foreach döngüsü kullanıldığında iterasyonlarda 
  sırasıyla 3,4 ve 5 sayıları elde edilecektir. Buna göre aşağıdaki kod parçası 
  ekrana 345 yazacaktır. 
    | public 
        class 
        Sınıf {
 public int foreach()
 {
 yield 
        3;
 yield 
        4;
 yield 
        5;
 }
 }
 
 
 |  
 
 
 
   
  Çoğu durumda foreach 
  yapısı ile sınıfımızın içindeki bir dizi üzerinde iteratif bir şekilde dolaşmak 
  isteyeceğiz. Bu durumda foreach bildirimi içinde ayrı bir foreach döngüsü aşağıdaki 
  gibi kullanılabilir. 
    | Sınıf deneme 
        = new Sınıf(); 
 foreach(int 
        eleman in deneme)
 {
 Console.Write(eleman);
 }
 
 
 |  
 
 
 
   
   
    | public 
        class 
        Sınıf {
 private int[] elemanlar;
 
 public int foreach()
 {
 foreach(int 
        eleman in elemanlar)
 {
 yield 
        eleman;
 }
 }
 }
 
 
 |  Yukarıdaki Sınıf nesneler ile foreach döngüsü kullanıldığında her bir iterasyonda 
elemanlar dizisinin bir sonraki elemanına ulaşılır.  
Gördüğünüz gibi 
  programcının bildireceği sınıflar da foreach döngüs yapısını kullanabilmek için 
  eskiden olduğu gibi IEnumerator arayüzün uygulamaya gerek kalmamıştır. Bu işlemi 
  derleyici bizim yerimize yapar.
 
 3 - Anonymous Metotlar(İsimsiz Metotlar)
 
 
 İsimsiz metotlar, 
  bir temsilciye ilişkin kod bloklarını emsil eder. Bildiğiniz gibi temsilciler 
  yapısında metot referasnı tutan veri yapılarıdır. Bir temsilci çağrımı yapıldığında 
  temsilcinin temsil ettiği metot çalıştırılır. Özellikle görsel arayüzlü programlar 
  yazarken event tabanlı programlama tekniği kullanılırken temsilcilerin kullanımına 
  sıkça rastlanır. Örneğin bir Button nesnesine tıklandığında belirli bir kod 
  kümesinin(metot) çalıştırılması için temsilci veri yapısından faydalanılır. 
  Sözgelimi Button nesnesinin tıklanma olayı meydana geldiğinde Click isimli temsilcisine 
  yeni bir temsilci atanır. Ne zaman button nesnesinin Click olayı gerçekleşse 
  ardından hemen temsilcinin temsil ettiği metot çağrılır. Buna bir örnek verecek 
  olursak;
 
 
 
 
   
  Yukarıdaki koddan 
  da görüldüğü üzere temsilci ile temsilcinin temsil ettiği metotlar ayrı yerlerdedir. 
  İsimsiz metotlarla bu işlemi biraz daha basitleştirmek mümkündür. Temsilci oluşturulduktan 
  sonra açılan ve kapanan parantezler arasına temsilci çağrıldığında çalıştırılacak 
  kodlar yazılabilir. Yukarıdaki örneği isimsiz metot ile yapacak olursak : 
    | public 
        class 
        Form {
 Button dugme;
 
 public Form
 {
 dugme 
        = new Button();
 dugme.Click += new 
        EventHandler(OnClick);
 }
 
 void OnClick(object 
        sender, EventArgs e)
 {
 ....
 }
 }
 
 
 |  
 
 
 
   
   
    | public 
        class 
        Form {
 Button dugme;
 
 public Form
 {
 dugme 
        = new Button();
 dugme.Click += new 
        EventHandler(object sender, EventArgs e);
 {
 //çalıştırılacak 
        kodlar.
 };
 }
 
 }
 
 
 |  Tanımlanan kod bloğundan sonra noktalı vürgülün eklenmiş olduğuna dikkat edin. 
Temsilci bloğundaki kodlar normal metotlardan biraz farklıdır. Normal kod blokları 
ile benzer özellikler taşır. Yukarıdaki temsilci kod bloğunda, blok dışında tanımlanan 
değişkenlere erişebilmek mümkündür. Ayrıca olay argümanlarının da(sender,e) EventHandler 
türünün parantezleri içinde yazıldığınıda dikkat edin. Bir önceki versiyonda olay 
argümanlarının yerine temsil edilen metodun ismi yazılmıştı.
 
 Peki isimsiz metotlar nasıl çalıştırılmaktadır? İsimsiz metot tanımı ile karşılaşan 
derleyici tekil isme sahip bir sınıf içinde tekil isme sahip bir metot oluşturur 
ve isimsiz metot gövdesindeki kodlara bu tekil metot içinden erişilir. Temsilci 
nesnesi çağrıldığında, derleyicinin ürettiği bu metot ile isimsiz metodun bloğundaki 
kodlar çalıştırılır.
 4 
  - Partial Types (Kısmi Türler)
 
 Kısmi türler yardımıyla bir sınıfın elemanlarını farklı dosyalarda saklamak 
  mümkündür. Örneğin Dosya1.cs ve Dosya2.cs aşağıdaki gibi olsun.
 
 
 
 
   
   
    | //Dosya1.cs 
 public partial class 
        deneme
 {
 public void Metot1
 {
 ...
 }
 
 }
 
 
 |  
 
   
   
    | //Dosya2.cs 
 public partial class 
        deneme
 {
 public void Metot2
 {
 ...
 }
 
 }
 
 
 |  Yukarıdaki iki dosyayı aynı anda derlediğimizde eğer kısmi türler kavramı olmasaydı 
derleme zamanında hata alırdırk. Çünkü aynı isim alanında birden fazla aynı isimli 
sınıf bildirimi yapılmış. Halbuki kısmi türler ile bu iki sınıf bildirimi aynı 
sınıf olarak ele alınır, ve birleştirilir. Yani deneme isimli sınıfın Metot1() 
ve Metot2() adında iki tane metodu olmuş olur.
 
 Bir türe ait elemanları tek bir dosya içinde toplamak Nesne Yönelimli Programlama 
açısından her ne kadar önemli olsada bazen farklı dosyalarla çalışmak kodlarımızın 
yönetilebilirliğini artırabilmektedir.
 Not : Bu yazı "MSDN Magazine" deki "Future Features of C#" başlıkla bildiri 
  baz alınarak hazırlanmıştır.
 
 
 
                Makale:C#'ın Gelecekteki Özellikleri  C#, Visual C# ve .NET Sefer Algan
 | 
        
            |  | 
        
            |  | 
        
            | 
                    
                        
                            
                        
                            Eklenen Son 10 
                            Bu Konuda Geçmiş 10 
                        
                            Bu Konuda Yazılmış Yazılmış 10 Makale Yükleniyor
                         
                            Son Eklenen 10 Makale Yükleniyor
                         
                            Bu Konuda Yazılmış Geçmiş Makaleler Yükleniyor
                         | 
        
            |  |