C# LINQ Architecture and New C# Features

Merhaba arkadaşlar,

Bu yazımda C# programlama dilinde LINQ Mimarisi ve Yeni C# özellikleri neler? Bunların bize sağladığı yararlar neler ve LINQ yaklaşımını esas alarak yapabileceklerimizden bahsetmek istiyorum.

Bu yazıda bahsedilecek konu başlıkları

  • LINQ (Language Integrated Query: Dile Entegre edilmiş Sorgu Yapısı)
    • LINQ Veri Sağlayıcıları ( LINQ to Object, LINQ to SQL, LINQ to XML)
  • C# Yeni Özellikler
    • var bildirim değişkenleri ve Tip Çıkarsama (type inference)
    • İsimsiz Tipler (Anonymous Type)
    • Nesne ve Koleksiyon İlklendirme (Object and Collection Initializer)
    • Otomatik Özellik Bildirimi (Automatic Property)
    • Genişletme Metotları (Extension Methods)
    • Lambda İfadeleri (Lambda Expression)
    • Kısmi Metotlar (Partial Methods)
  • İfade Ağaçları (Expression Trees)
  • Ertelenmiş Çalışma (Deffered Execution)

LINQ (Language Integrated Query: Dile Entegre Edilmiş Sorgu Yapısı)

https://www.codeproject.com/Articles/19154/Understanding-LINQ-C

LINQ Mimarisi 3 katmandan oluşmaktadır. En üstteki katman LINQ yapısını kullanacak olan programlama dilleridir. Ortadaki katman LINQ sorgularının işlendiği katman ve en alttaki katman ise LINQ için çetişli data-source'lar mevcut ve bu data-source'lar IEnumerable <T> ve IQueryable<T> şablon tipli arayüzleri uygularlar. Bu data-soruce ile LINQ Query katmanı arasında iletişim kuran LINQ provider (sağlayıcı). LINQ Provider sayesinde LINQ sorguları veri-kaynağı tarafından anlaşılabilir hale geliyor.

C# programlama diline eklenen yeni özelliklerin temelinde yatan şey nesne yönelimli programlama ile fonksiyonel programlamayı birleştirmektir.

LINQ C# 3.0’daki en önemli yeni bir özellik diyebiliriz. Kısaca C# programlama dili ile herhangi bir üçüncü bileşene ihtiyaç duymadan dilin kendi söz-dizimi ile veriye erişimi ve veri üzerindeki işlemleri kolaylaştırmaktadır. Bahsedilen veri çok çeşitli olabilir; veri-tabanındaki ilişkisel veri olabilir, hafızada bir liste veya XML dosyası olabilir. LINQ, veri erişim mekanizmasının veri tipinden bağımsız olarak standart hale getirilmiş söz dizimi topluluğuna verilen genel bir isimdir. LINQ barındırdığı çeşitli soyutlama yapısı ile farklı veri kaynaklarına da erişim sağlayabilmektedir.

LINQ Veri Sağlayıcıları

  • LINQ to Object: Hafızada bulunan List<T> tipinde koleksiyon yapılarının sorgulanması ve işlenmesi amacı ile geliştirilmiştir.
  • LINQ to SQL: SQL Server gibi ilişkisel veri formatında veriyi tutan ortamlardaki verilere erişmek ve o veriler üzerinde işlemler yapmak için geliştirilmiştir. Literatürde bu yaklaşıma ORM (Object Relation Map) denilmektedir.
  • LINQ to XML: XML veri kaynaklarına herhangi bir parser (ayıklayıcı) kullanılmadan hızlı bir şekilde erişmek için geliştirilmiştir. Temel amacı XML tipli veriler ile çalışırken üretkenliği artırmak.

Neden LINQ

Klasik veri-tabanında veriye erişim yönetimindeki temel sorunları irdeleyecek olursak

  • Derleme sırasında sorgu kontrolü yapılamamaktadır. Kod ortamında sorgular string yapıda oldukları için sorguların yazımında da üretkenlik düşmektedir.
  • Sorgular içerisinde gevşek bağlı (loosely coupled) argümanlar bulunmaktadır. Bu yüzden çalışma zamanında veri-tipi uyuşmazlığı ile karşılaşırız.
  • Tip kontrolü yapılmadığı için çalışma zamanında hatalar meydana gelmektedir.

Platform bağımsızlığından kaynaklanan bu farklıları ortadan kaldırmak ve programcıyı veri-bağımsız hale getirmek için C# 3.0 ile birlikte LINQ yapısı geliştirilmiştir.

ORM (Object Relation Mapping) Yaklaşımı

ORM bir başka ismi ile O/R Mapping, ilişkisel veri-tabanını ile nesneye yönelik programların arasında bir tür köprü görevini üstlenen ve ilişkisel veri-tabanındaki bilgilerimizi yönetmek için nesnel modellerimizi kullandığımız bir tekniktir.

Klasik veri erişim sorunlarından sonra LINQ erişim sayesindeki kazanımlar

  • O/R Mapping yaklaşımı sayesinde nesnel düşünmekten feragat etmeden veri erişim işlemlerini gerçekleştirmek
  • Tip Güvenliğini sağlamak
  • Veri-tabanı kavramını nesnel olarak tanımlamak
  • Herhangi bir sorgu yazmadan veri-tabanına erişim sağlayabilmek
  • söz-dizimi olarak hatalı bir sorgu yazmamızı engelliyor.

LINQ to Object Mimarisi

LINQ to Object sağlayıcısı hafızada bulunan ve IEnumerable<T> arayüzünü uygulayan bütün koleksiyonlarda uygulayabileceğimiz ve .NET 3.5 ile birlikte gelen yapıdır.

Yukarıdaki örnek uygulamada aslında for döngüsü ile de bu işlemi yapabilirdik. Ancak LINQ ile bu işlem hem daha basit hem okunaklı olmaktadır. yukarıda from, where ve select gibi LINQ yapıları ile tıpkı SQL ortamında sorgulama yapılabilir.

Bu yapılar ve daha fazlası aslında aşağıdaki gibi de kullanabilmekteyiz.

Bu metot yapılar genişletme metotları (extension method) sayesinde oluyor. Yukarıdaki kod bloğunun çalışabilmesi içinde

bildirimin kod içerisinde olması gerekiyor.

Select ve Where metodundaki parametreler aslında birer delegate olduğunu bilmek gerekir. Bu sayede daha eski CLR 2.0 yapısına dönüştürülürse aşağıdaki çağrım olacaktır.

C# Yeni Özellikler

var bildirimli Değişkenler

C# 3.0 ile birlikte bir metot içerisinde tanımlanacak değişkenler tipleri bildirilmeden tanımlanabilmektedir.

Dile eklenen var anahtar sözcüğü sayesinde değişkene atanan değerin tipinden çıkarsama (type inference) yapılarak derleme aşamasında IL içerisine gerçek tipli bildirim eklenir.

Bu noktada bir değişkeni object olarak tanımlamak ile var bildirimi ile tanımlamak arasında hiçbir benzerlik yoktur. Çünkü object ile tanımlanan değişkenlerin tür kontrolleri ve ayrışımı çalışma sırasında dinamik olarak yapılır. var ile bildirimlerde ise bu işlem derleme zamanında olmaktadır.

var anahtar bildiriminin dile eklenmesinin temel nedeni yukarıdaki basit veri tipleri tanımlanırken değil de özellikle şablon tipli karmaşık veri tiplerinin tanımlanmasıdır. Özellikle LINQ içerisinde Select ifadesi ile geriye döndürülen tipin ne olduğu belli olmadığı durumlarda (anonymous type) var bildirimi kullanılması kaçınılmazdır. Zaten bu özellik daha çok bu amaç için eklenmiştir.

yukarıdaki örnekte Select metodunda geri döndürülen nesnenin adı belli değildir. Bu durumda geriye isimsiz sınıf dönmüştür. Bu durumda isimsiz sınıfları var bildirimine atamalıyız.

var anahtar kelimesi ile bildirilen değişkenlerin kısıtları;

  • var ile bildirilen değişkenler sadece local değişkenler olabilir. Yani bir sınıf üye elemanı olamaz
  • var ile bildirilen değişkene ilk değer atama işlemi zorunludur. Aksi durumda tip çıkarsaması yapamayacağından derleme zamanında hata oluşur.
  • Bir metodun herhangi bir parametresi yada geri dönüş değer var tipinde olamaz
  • var ile bildirilen değişkene sonradan farklı türden değişken atanamaz

Dizi Tiplerinin Çıkarsama Yolu ile Belirlenmesi (Type Inference)

C# 3.0 ile birlikte gelen yeniliklerin bir tanesi dizi tiplerinin dizi içerisinde bulunan elemanların tiplerinden çıkarabilmesidir.

Dizi elemanları aynı tipten veya bilinçsiz (implicit) bir şekilde birbirine dönüştürülebilen tipten olması gerekiyor.

İsimsiz Tipler (Anonymous Types)

C# 2.0 ile birlikte gelen isimsiz metotlardan sonra C# 3.0 ile birlikte LINQ yapısında kullanabilmek için isimsiz tipler gelmiştir. Öyle bir veri-tipi düşününki içinde birbiri ile ilişkili özellik olsun ama adı olmasın 🙂

İsimsiz tip tanımlama söz-dizimi

ILDASM aracı ile IL kodlarına baktığımızda IL kodlarına isimsiz bir tipin enjekte edildiğini görürüz. (<>f__AnanymousType0’2′)

İsimsiz Tipler diğer .NET sınıflarında olduğu gibi otomatik olarak object türünden türetilmiş bir sınıf olarak tanımlanır.

İsimsiz tiplerin en önemli kullanım yeri LINQ yapısıdır. Özellikle Select metodu ile geri döndürülen nesne eğer koleksiyon tipinden farklı ise işe yaramaktadır.

İsimsiz tipler ile ilgili diğer bir durum ise aynı kalıba uygun nesnelerin birbirine atanıp atanamayacağı. Aynı program içinde aynı özellik desenine sahip (isim ve tip bakımından) isimsiz tip nesneler birbirine atanabilmektedir.

Nesne ve Koleksiyon İlklendirme

C# 3.0 ile birlikte Nesne ve Koleksiyonları çok daha kolay bir şekilde oluşturabiliyoruz.

Nesne İlklendirme

Sınıflara ait nesneleri yaratırken yapıcı metot (constructor) kullanıyoruz. Eğer yazdığımız yapıcı metotların tek görevi özelliklerin veya değişkenlerin aramasını yapması ise bunu nesnemiz oluştururken süslü parantezler ile özelliklere ilk değerlerini yapabiliriz.

Koleksiyon İlklendirme

Aynı Nesne İlklendirmede olduğu gibi Koleksiyon nesnemizi oluştururken elemanlarını da süslü parantezler içinde belirleyebiliriz.

Sondaki Koleksiyon İlklendirme C# 6.0’dan itibaren geçerli olduğunu hatırlatayım!

Otomatik Özellik Bildirimi (Automatic Property)

C# 3.0 ile birlikte özellik tanımlama biçimi daha kolay hale geldi. Yalnızca erişim kontrol altına alınmak istendiği senaryolarda get ve set bloklarını tek tek yazmak ve özelliğe ait değişkeni tanımlamaya gerek yok.

Otomatik özellikler daha çok bir sınıfın özellik havuzu şeklinde kullanılması durumunda faydalı olmaktadır. Bu durumda C# derleyicisi derleme aşamasında otomatik özelliğe ait get ve set bloklarını IL koduna yerleştirir.

Department sınıf bildirimini içeren IL kodu aşağıdaki gibidir. Title değişkeni <Title>k__BackingField isminde adlandırılmış ve get ve set metotları da eklenmiş durumda.

Otomatik özellik tanımlama daha çok XML ve SQL yapısında verilere erişmek için kullanılan eşleştirme sınıflarının ( mapping class) bildiriminde kullanılır.

C# 6.0 sürümü ile birlikte otomatik özellik bildirimine yeni bir özellik gelmiştir. Buna göre otomatik özellik bildirimine aynı sınıflar değişkenlerinde olduğu gibi ilk değer ataması yapılabilmektedir.

Genişletme Metotları (Extension Methods)

Genişletme metotları LINQ mimarisi için nesne yönelimli programlamanın dışında olan bir özellik eklenmiştir. Genişletme metotları mevcut bir veri tipine yeni bir metodun eklenmesini olanak sağlar. Böylece türetme olmadan ve mevcut tipin kullanılmasını engellemeden aynı tiplere yeni metotlar eklenebilir. Yine bu özelliğin çıkış noktası LINQ yapısında bulunan Select ve Where gibi fonksiyonların mevcut koleksiyonlarla çalışmasına olanak sağlamaktır.

IEnumerable<T> üzerinde çağrılan genişletilmiş metotlar bulunmaktadır. Aşağıda List<string> nesnesi üzerinde Where metodunu çağrıldığını görebiliriz. Where metodu aslında genişletme metodudur ve IEnumerable<T> üzerinden çağrılmaktadır.

Where genişletme metodunun prototipine baktığınızda şablon tipli olduğunu görüyoruz. Where metodu TSource tipinde bir şablon tip parametre alıyor ve geri dönüş olarak yine bu tipte bir koleksiyon tabanlı nesne dönüyor. İlk parametre this ile bildirilen IEnumerable<TSource> ile asıl genişletilmek istenen kaynağı bildiriyoruz. İkinci parametre olan predicate yaptığımız koşulu bir temsilci vasıtasıyla gönderiyoruz. Bu sayede filtrelemek koşuluna göre geriye bir koleksiyon dönecektir.

System.Linq.Enumerable isim alanında bulunan bazı extension metotlar.

Genişletme metotları ile ilgili önemli durumlar;

  • Genişletme metotları yapı, sınıf ve arayüzlere uygulanabilir
  • Genişletme metotların bildirimi static bir sınıf içinde tanımlanmalıdır.
  • Genişletilecek tip bildirimi, Genişletme metodunun ilk parametresi olmak zorundadır ve önüne this anahtar sözcüğü gelmelidir.
  • Genişletme metoduna erişilebilmesi için ilgili yerde genişletme metoduna ait sınıfın erişilebilir olması gerekir.
  • Sınıfın genişletme metodunun prototipine uygun başka bir metodu varsa çağrım sırasında öncelik sınıfın kendi metodudur.
  • Genişletme metotları da normal metotolar gibi aşırı yüklenebilir (overload)
  • Bir tipe farklı static sınıflar içinde genişletme metodu eklenebilir. Bu durumda farklı noktalardaki metotların imzaları farklı olmalıdır.
  • Genişletme metotları sadece C# diline ait değildir.
  • Taban sınıfa eklenen genişletme metodu, taban sınıftan türeyen sınıflardan da erişmek mümkün. Örneğin object sınıfına eklenmiş olan genişletme metodu bütün tiplerde gözükür.

Genişletme metotları LINQ yapısı dışında da kullanışlı olabilecek bir özelliktir. Kendi genişletme metotlarımızı da yazabiliriz.

Genişletme metotları normal yoldan da çağrılabilir.

Lambda İfadeleri (Lambda Expression)

Lambda ifadeleri C# 3.0 ile birlikte eklenmiş en önemli özelliklerden biridir. Lambda ifadeleri ile LINQ yapısını kolay ve anlaşılabilir olarak kullanılabilmesi için olan bir özelliktir. Lambda ifadeleri fonksiyonel programlamada kodun bir veri olarak temsil edilmesini sağlayan ifadelerdir. Lambda ifadeler C# yeni olmasına rağmen aslında Lisb, Python ve Schema gibni programlama dillerinde var olan bir özelliktir.

Lambda ifadelerin algoritmayı daha doğrusu kodu bir veri kaynağı olarak görmesinden kaynaklı gücü sayesinde algoritmaların daha hızı yazılmasını sağlar.

Peki nedir bu Labmda? C#’ta lambda ifadeler delegate değimiz yani temsilcilerle doğrudan ilgilidir. Lambda ifadeleri delegate nesnesi bekleyen fonksiyon parametrelerini metot bildirimi yapmaksızın kullanmamızı sağlıyor.

FindAll metodu aslında bir delegate parametresi bekliyor. Lambda ifadesi sayesinde temsilciler birer veriymiş gibi fonksiyonlara parametre olarak gönderilebilir.

Tanımlama biçimleri

Kalıp: (parametreler) => (ifade gövdesi) ifade gövdesinde üretilen sonucun tipi neyse geri dönüş değeri o olur.

Expression Lambda

(input-parameters) => expression

Statement lamda

(input-parameters) => { <sequence-of-statements> }

Lambda Gövdeli Metot ve Özellik Bildirimi

C# 6.0’dan itibaren eğer set bloğunda yazacağımız kod ya da metot gövdesinde yazacağımız kod 1 veya 1^den az ise lambda ifadelerini kullanabiliriz.

C# geliştiricilerinden gelen yoğun istek üzerine C# 7.0 versiyonu ile birlikte metot ve özelliklere ek olarak lambda gövdeli yapıcı metot, yıkıcı metot ve get set blokları için kullanabiliriz.

Kısmi Metotlar ( Partial Methods)

C# 2.0 ile birlikte bir sınıfı birden fazla fiziksel dosyaya bölen kısmi sınıflar vardı. Böylece otomatik olarak yazılan kodlar ile programcının yazmış olduğu kodlar birbirinden ayrılmış oluyordu.

Kısmi metot kavramı C# 3.0 ile birlikte gelen bir kavramdır. Kısmi metotlar yine özellikle otomatik olarak üretilen kod blokları ile bo kod bloklarının kullanan istemci uygulama arasında entegrasyon (mesaj alaış-veriş) sağlanmaktadır. Kısmi metotlar şu anda daha çok LINQ ifadeler ile (LINQ to SQL) sıkça kullanılmaktadır.

Temel kullanım amacı Entity denilen tabloyu temsil eden yapılarda meydana gelen değişiklerde uygulamayı haberdar etmesidir. Normal şartlar bu event mekanizması ile yapılabilir ancak bu hem maliyetli olacaktır hem de uygulaması zor olacaktır.

Kısmi metotlar uygulamada otomatik olarak kod üretilen bir yapı var ise anlamlı olur!

Bu yapıya örnek verelim;

Otomatik olarak üretilen bu kod aşamasından sonra PersonManager partial sınıf oluşturulup kısmı metotlar bildirilebilir.

Bu aşamada kod derlendikten sonra çalışma zamanında ilgili kısmı metotlar çalışacaktır.

Kısmi metotlar ile ilgili bilinmesi gerekenler

  • Kısmi metotlar sadece kısmi tipler için geçerlidir.
  • Kısmi metotların gövdesi her zaman olmak zorunda değildir. Gövdesi olmayan kısmi metotlar IL dahil edilmezler.
  • Kısmi metotlar partial olarak bildirilirler
  • Kısmi metotların geri dönüş değeri void olmak zorundadır.
  • Kısmi metotlar override, virtual, abstract, new, sealed anahtar sözcüğü ile bildirilemezler
  • Kısmi metotlar static olabilirler
  • Kısmi metotlar erişim belirleyicileri ile belirlenmezler (Varsayılan olarak private)
  • Kısmi metotların parametreleri ref, params, this gibi anahtar sözcükleri ile tanımlanabilirler

LINQ to SQL ile Veritabanı Sorgulama

LINQ mimarisi genişleyebilir bir mimaridir. LINQ ifadeleri arka planda IQueryable arayüzü ile uyumlu nesneler üretir. Bu arayüz içerisinde LINQ ifadeleri ağaç şeklinde oluşmasını sağlayan Expression nesnesi bulunmaktadır. IQueryProvider isimli arayüz sayesinde dinamik olarak sorgu cümleleri çalışma zamanında oluşturulabilir.

LINQ to SQL yapısında sorgular çalışma zamanında t-sql yapısına çevrilerek veri-tabanı üzerinde direkt olarak sorgulama yapılabilmesine olanak sağlar. Bu gelişme araçları kullanılarak üçüncü parti firmalar ya da kişiler kendi özgü LINQ sağlayıcılarını geliştirmişlerdir.

Linq To SQL Örnekleri

LINQ to SQL yapısı ile ilgili sınıflar, nitelikler ve arayüzler System.Data.Linq ve System.Data.Linq.Mapping isim alanları altındadır.

Yukarıdaki sorgu ifadesinde foreach iterasyonu başladığında IQueryProvider nesnesi devreye girerek LINQ sorgu ifadesinden t-sql sorgu cümlecikleri oluşturur. Yukarıdaki ifadeden sonra üretilen t-sql sorgusu;

LINQ to XML Altyapısı

LINQ mimarisinin XML verilerini sorgulama ve üzerinde işlem yapması için uyarlanmış versiyonudur. LINQ to XML ile ilgili sınıfların tamamı Syste.Xml.Linq isim alanındadır.

LINQ to XML sınıfları yardımıyla XML verisi hazırlama

Oluşan Xml

İfade Ağaçlar (Expression Trees)

LINQ mimarisinin genişleyebilir olmasının altında yatan şey .NET Frameowrk 3.5 ile birlikte gelen ifade ağaçlarıdır. İfade ağaçları sayesinde çalıştırılabilir kod ağaç yapısı şeklinde veri olarak bellekte saklanır. Bu sayede LINQ sorgu ifadeleri derleme zamanında veri olarak ifade edilir. Dolayısıyla çalışma zamanında bu ağaç yapısı çözümlenir ve üretilmesi gereken kod (sql, xpath ya da metot çağrımı) belirlenir.

Kısacası LINQ mimarisinin genişleyebilir yapıda sunabilmesini sağlayan ifade ağaçlarıdır. İfade ağaçlarını temsil eden sınıflar System.Linq.Expression isim alanında bulunurlar.

Expression yapıları için kod örneği

yukarıdaki örnekte Expression yapısı kullanılarak lambda yardımı ile x değerini karesi alınıyor. elimizde run-time çalışacak ağaç yapısı artık mevcut. Daha sonra ağaç yapısı derlenerek temsilciye aktarılıyor ve bu sayede elimizde artık bir sayının karesini alabilen squareFunc metodu oluşuyor.

Expresison yapısını kullanılarak if-else-then bloklarına sahip kodlarda yapmak mümkün. Expresison sayesinde dinamik kodlar yazılabilir.

Expression yapıları ile dinamik kod üretmekte mümkün.

Ertelenmiş Çalışma (Deferred Execution)

LINQ ile birlikte gelen ağaç yapısı sayesinde LINQ sorguları kod içerisine geçtiği noktada icra edilmezler. LINQ sorgularında geri döndürülen IQueryable arayüzüne sahip nesne kullanıldığı ilk noktada çağrılır. İlk kullanımdan kasıt; foreach itereasyonu veya metot çağrımı olabilir. Bu duruma ertelenmiş çalışma (Deferred Execution) denir.

Bunu daha iyi anlamak için örnek üzerinden gidelim. Elimizde IQueryable türünde verilerimiz var ve LINQ yapısı ile o veriler içinden belirlediğimiz alanı getirmek istiyoruz. Filtreleme yaparken filtrelenecek alanı değişkene (companyName) atayacağız ve LINQ sorgusu bittikten sonra kısıt değişkeninin değerini değiştirip foreach döngüsü ile en son hangi kısıt değeri ile LINQ sorgumuzun filtrelendiğini Console uygulamasına yazdırıyor olacağız.

Yukarıdaki örnekte ertelenmiş çalışma mekanizmasına göre sorgulama işlemi foreach iterasyonunda gerçek anlamda çalışacağı için companyName değişkenine tam o noktada değeri atanmaktadır. Yani companyName değeri “Trey Researc” olan şirket dönecektir.

Eğer ertelenmiş çalışma mekanizmasını devre dışı bırakmak istiyorsak LINQ sorgusunun sonucunda dönen değer üzerinden ToList(), ToDictionary() gibi metot çağrımı yapmalıyız.

Ertelenmiş çalışma mekanizmasını dere dışı bıraktıktan sonra companyName değeri olan “Coho Winery” listelendiğini görürüz. Yani ToList() yardımı LINQ sorgusu icra ediliyor.

Bu yazımda C# LINQ Mimarisi ve Yeni Gelen C# Özellikler hakkında edindiğim bilgileri paylaşmaya çalıştım. Umarım faydalı olmuştur 🙂

Bir sonraki yazımda görüşmek üzere 🙂

Kaynaklar:

Her Yönüyle C# 7.0, Sefer Algan, Pusula Yayincilik

https://docs.microsoft.com/tr-tr/dotnet/csharp/programming-guide/concepts/linq/

Leave a Comment