Microsoft’un Visual Studio 2008 ve .NET Framework 3.5 ile beraber kullanıma sunduğu LINQ (Link şeklinde okunur), İngilizcesi Language INtegrated Query, Türkçesi Programlama Diliyle Bütünleştirilmiş / Entegre Edilmiş Sorgu olan, Haskell, XML, HTML ve SQL programlama dilleri gibi bildirimsel (Declerative) yazım şekli (sözdizimi, syntax) kullanan bir bileşen / teknolojidir.
Bildirimsel programlamanın karşılığı olarak kullanılabilecek program yazma düzeni (dizilimi) yordamsal programlamadır (yöntem sözdizimi). Yordamsal programlamada program, fonksiyon (function) veya altprogram (subroutine) denilen yordamlara ayrılarak oluşturulur. Yordamsal programlamada problemin nasıl çözüleceği / işin nasıl yapılacağı altprogramlar ve uygun bir akış tanımlanarak programcı tarafından bildirilir. SQL, XML ve HTML gibi bildirimsel programlamada ise programcı işin nasıl yapılacağından çok ne yapılacağını söyler.
LINQ, programlama diline SQL (Structured Query Language – Yapısal Sorgu Dili – Veritabanındaki kayıtlarla ilgili işlemleri yapmak için kullanılan bir bildirimsel programlama dili.) biçimi veri sorgulama yeteneği ekler. LINQ ile SQL Server Veritabanları, XML belgeleri, ADO.NET Veri Kümeleri (Dataset) ve hafızada bulunan koleksiyon türlerindeki verileri sorgulayabilirsiniz. Daha fazla bilgi için LINQ Sağlayıcıları (LINQ Providers) yazımı inceleyin.
Visual Studio LINQ sorguları için Akıllı Kod Sezme’yi (Intellisense, Intelligent Sense veya Intelligent Code Sense ifadelerinin kısaltılmışıdır. Türkçe’ye Akıllı Kod Sezme olarak tercüme edebileceğimiz bu özellik, kod yazarken nokta ve boşluk yazdığınızda karşınıza çıkan ve yazılabilecek ifadeleri görüntüleyen listeleri ifade eder. Bu listelere akıllı denmesinin nedeni, kullanılması muhtemel olmayan ifadeleri göstermemesi, seçimlerinde öncelik ve yazılanı içeren ifadeleri gösterme vb. kabiliyetleri olmasıdır.) ve LINQ ifadelerinin derleyici kontrolünü de destekler. Yani kod yazarken kullanılması muhtemel ifadeleri ve derlemeye gerek kalmadan derleyici hatalarını görebilirsiniz.
Peki nasıl bir şey bu LINQ sorgusu? Aşağıdaki kodu ve açıklamaları inceleyin.
sing System; using System.Collections.Generic; using System.Linq; namespace LinQLinQ { class Program { static void Main(string[] args) { var liste = new int[] { 1, 3, 33, 44, 56, 101 }; var tekler = from sayi in liste where sayi % 2 == 1 select sayi; foreach (var tekSayi in tekler) { Console.WriteLine(tekSayi); } Console.ReadKey(); } } }
Açıklamalar:
Satır 11: Bir tamsayı dizisi oluştur ve bazı elemanlar ata.
Satır 13: “tekler” isimli bir değişken tanımla…
Satır 14: “liste” dizisinin elemanlarını gez ve gezerken sırası gelen her bir elemanı “sayi” değişken adı ile tanımla (bu değişkenlere seri değişkeni denir),
Satır 15: “sıradaki eleman % 2” (yani sayi Mod 2) işlemi yapıldığında 1 sonucunu verenlerin (tek sayıların)…
Satır 16: kendilerini seç ve seçtiklerini “tekler” koleksiyonuna ekle.
Satır 18, 19, 20: “foreach” döngüsü kullanarak listenin elemanlarını tek tek gez ve çıktıya yaz.
14, 15 ve 16. satırlarda görülen LINQ sorgusu hafızadaki (veya bir veri kaynağındaki) veri kümesinden program içinde SQL gibi sorgulama yaparak, seçilen verilerden oluşan yeni bir liste elde etmemizi sağlar.
Bir sorgu değişkenine (Örneğimizde “tekler”), standart bir sorgu atandığında sorgu hemen çalıştırılmaz. Sorgunun çalıştırılması için “foreach” döngüsü içinde sorgu değişkeninin kullanılması veya listedeki eleman sayısının istenmesi (.Count()), listenin List<T> yapısına çevrilmesi (.ToList()) vb. bir istek lazımdır. Sorgu bu aşamada çalıştırılır. Buna Ertelenmiş Çalıştırma (Deferred Execution) denir. Bir sorgu çalıştırılana kadar gerekli olan her şey bir ifade ağacında (Expression Tree) tutulur. Bizim örneğimizdeki sorgu da “foreach” döngüsünün başladığı satıra gelindiğinde çalıştırılacaktır.
LINQ sorgularını çoğunlukla örnekteki gibi SQL sorguları biçiminde görürsünüz. LINQ sorgularının bu bildirimsel yazım şekli bize sağlanmış bir çeşit kolaylıktır. Ancak bu sorguların tamamı derleme sırasında yöntem (method) çağrılarına dönüştürülürler. Bu yöntem çağrılarının benzerlerini biz de programlamada kullanabiliriz. Yani LINQ sorgularını yordamsal programlama / yöntem sözdizimi ile de yazabiliriz. Üstteki örneğin yöntem sözdizimi ile yazılmış hali şöyledir.
using System; using System.Collections.Generic; using System.Linq; namespace LinQLinQ { class Program { static void Main(string[] args) { var liste = new int[] { 1, 3, 33, 44, 56, 101 }; var tekler = liste.Where(sayi => sayi % 2 == 1); foreach (var tekSayi in tekler) { Console.WriteLine(tekSayi); } Console.ReadKey(); } } }
Yöntem sözdizimi ile LINQ sorgularını tam olarak anlayabilmek için bu konudan sonra LINQ Yöntem Sözdizimi ve Lambda İfadeleri yazılarımı okumanızı tavsiye ederim.
Dikkat etmişsinizdir. Şimdiye kadarki değişken tanımlarımızın tümünü “var” ile yaptık. “var” ile değişken tanımladığınızda, tanımladığınız değişkenin tipi derleyici tarafından derleme sırasında tespit edilir. Bu tür tanımlamaya Implicitly Typed (Örtülü Tip Ataması) denir. “var” ile tanımlanan değişkenin türü daha sonra değiştirilemez (İsimsiz / anonim tiplerde ise nesne oluşturulduktan sonra nesnenin özelliklerine erişebilmek için “var” kullanmak zorunludur, zaten başka da yolu yoktur.).
“var” ile tanımlama yaptığınızda daha az yazar ve geliştirme zamanındaki hatalardan bir parça kaçınabilirsiniz ve gerçekten işe yarar bir yöntemdir. LINQ sorguları gibi başlangıçta karmaşık gelebilecek tanımlamalar ve tipler içeren işlemleri gerçekleştirirken “var” ile değişken tanımlayarak burada kullanılan tipler hakkında çok iyi bilgi sahibi olmasanız da programlama yapabilirsiniz. Ancak, tabii ki tipleri bilmek zorundayız.
Alttaki örnek aynı sorgunun “var” yerine değişken tipleri bildirilerek yapılmış halidir.
using System; using System.Collections.Generic; using System.Linq; namespace LinQLinQ { class Program { static void Main(string[] args) { int[] liste = new int[] { 1, 3, 33, 44, 56, 101 }; IEnumerable<int> tekler = from sayi in liste where sayi % 2 == 1 select sayi; foreach (int tekSayi in tekler) { Console.WriteLine(tekSayi); } Console.ReadKey(); } } }
LINQ sorguları genelleyici (generic) tabanlıdır. LINQ konusunu tam olarak kavrayabilmek için genelleyiciler (generics) ve arayüzler (interfaces) hakkında bilgi sahibi olmanız gerekir. Mümkün olursa, genelleyici va arayüz konusuna baktıktan sonra bu yazıya devam etmenizi tavsiye ederim. Ya da aşağıdaki kısa açıklamaları okuyun.
Genelleyiciler (Generics)
List<T> gibi küçük ve büyük işaretleri arasında adı sadece T olan veya T ile başlayan bir kaç ifade görürseniz bunlar genelleyici tiplerdir. “List sınıfı” gibi bir genelleyici koleksiyon (elemanlara sahip bir liste) sınıfın örneğini oluşturduğunuzda, T’yi listenin tutacağı sınıfın tipi ile değiştirirsiniz. Örneğin List<String>, String türünden değerlerin bir listesini, List<Musteri>, Musteri sınıfının örneklerinin bir listesini tutar. Genelleyici List sınıfı, tuttuğu listeler üzerinde, aralarında LINQ sorgularının da bulunduğu çeşitli işlemler yapmanıza olanak tanır. Ayrıca List<T> sınıfı, ArrayList gibi genelleyici olmayan koleksiyonlarda olduğu gibi çalışma zamanında tip zorlaması (type casting) yapmanıza gerek bırakmadığı için gayet kullanışlıdır. Tabii ki T’nin tipi dışında bir türü T’nin türüne çevirmeden listeye ekleyemezsiniz. Genelleyiciler sınıflarda kullanıldığı gibi bir yöntem (fonksiyon) de olabilirler. Bu yöntemler parametre olarak bir genelleştirilmiş tip alırlar. Yani bu tür fonksiyonlara parametre olarak herhangi bir tip değer gönderebilirsiniz. T’nin yerine herhangi bir tip koyabilmenizden dolayı her tip ile kullanılabidiğini belirtmek amacıyla “genelleyici” (generic) dendiğini kabul edebilirsiniz.
Arayüz (Interface)
Bir arayüz (Interface) ile farklı türdeki sınıflara ortak özellikler ve metotlar ekleyebilir ve destekledikleri arayüz tipine dönüştürülerek (Explicit Conversion – Açık Dönüştürme) veya atanarak (Implicit Conversion – Örtülü Dönüştürme) sadece arayüzde tanımlanan özellik ve metotlarla sınırlı olmak üzere aynı sınıfmış gibi kullanabilirsiniz. Arayüz isimleri mecburi olmamakla birlikte bir I harfiyle başlar. Örnek olarak, .NET Sınıf kütüphanesinde bir çok sınıf IDisposable (“Dispose”, boşaltmak, ortadan kaldırmak, serbest bırakmak, “Disposable” ise boşaltılabilir, ortadan kaldırılabilir veya serbest bırakılabilir anlamındadır. Baştaki I bunun bir arayüz olduğunu söyler.) arayüzünü destekler. IDisposable arayüzünün Dispose isimli bir yöntemi vardır. IDisposable arayüzü destekleyen sınıflar arayüzde bulunan Dispose yöntemini barındırmak ve bu yöntemde nesne hafızadan atılacağı zaman kendisiyle ilgili yapılması gerekenleri (örneğin bir resmi hafızada tutuyorsa kendisini hafızadan atmadan önce tuttuğu resmi hafızadan silmek için gerekli kodları) bildirmek zorundadırlar. Bunun faydası şudur; Eğer Atık Toplayıcı (Garbage Collector – .NET programlarının çalışmasını sağlayan CLR / Common Language Runtime isimli program çalıştırma ortamının / sanal makinenin hafızada kullanılmayan boşta kalan nesneleri temizleyen modülü) veya siz IDisposable arayüzünü destekleyen tüm sınıf örneklerini (nesneleri) gerçek tipleri ve kullanımı ile ilgili hiç bir bilgiye sahip olmadan IDisposable türüne çevirip daha sonra Dispose metodunu çağırarak derinlemesine bir temizlik yapabilir, hafızayı daha iyi kullanabilirsiniz. .NET Framework Sınıf Kütüphanesinde genelleştirilmiş bazı sıralama özelliklerini kullanabilmek, listeleri normal sayısal ve alfasayısal sıralama ve hatta bunların dışında özel sıralamaya tabi tutabilmeniz için IComparable ve IComparer arayüzleri ve altta açıklanan IEnumerable<T> gibi pek çok arayüz vardır.
Ve LINQ’e devam…
Örnekte kullanılan tip olan IEnumerable<T> (Enumarable = Sayilabilir, elemanlarına tek tek ulaşılabilir) genelleyici arayüzü, genel koleksiyon sınıflarına sayılabilirlik özelliği ekler ve bu sayede koleksiyon sınıfı LINQ ve “foreach” ile kullanılabilir. Yani LINQ ve “foreach” ile kullanabildiğiniz koleksiyon sınıfları IEnumerable<T> arayüzünü uygular. Bu arayüzün, arayüzü uygulayan sınıfların uygulamaları gereken “GetEnumerator” (GetEnumarator = Sayiciyi Getir / Ver) isimli tek bir yöntemi vardır. LINQ ve “foreach” komutları koleksiyonun bir elemanı gerekli olduğundan bu yöntemi çağırarak elemanlara tek tek ulaşırlar.
LINQ sorguları sonucunda dönen değerin tipi IEnumerable<T> olacaktır. Kullandığınız sağlayıcıya ve sorguya göre bazen IQueryable<T> tipinde bir koleksiyon da elde edebilirsiniz.
Örnekte görüldüğü gibi tamsayılar tutan bir genelleyici listeden tek tamsayıları seçtiğimiz için, dönen elemanları tutan listenin tipini de IEnumerable<int> olarak bildirdik. Ancak LINQ Örnekleri sayfamızdaki Basit Select Örneği 1, Basit Select Örneği 2 ve Select İsimsiz Tip Örneği 1’i incelerseniz göreceksiniz ki, örnekteki “select” cümleciğinin yanında seri değişkeni yerine (Örneğimizde “sayi”), seri değişkeninin bir özelliğini, seri değişkeni ile yapılacak bir işlemin sonucunu veya bir isimsiz sınıfı da bildirip seçilmesini sağlayabiliriz. Bu durumda IEnumerable<int>’i IEnumerable<SeriDegiskenininTipi> olarak değiştirin veya isimsiz tip listesi elde ediyorsanız Select İsimsiz Tip Örneği 1’de açıklandığı gibi tip bildirmek yerine var kullanın.
Eğer şartımıza (where bölümü) uyan hiç eleman olmasaydı 0 elemanlı bir IEnumerable<int> koleksiyonu elde edecektik.
Şimdiye kadar kullandığımız sorgularda sorgu içinde değişken tipi bildirimi ile ilgili hiç bir ifade bulunmamaktadır. Çünkü derleyici her bir tipi doğru olarak belirler. Bu sebeple tip tanımlamasına standart sorgularda gerek olmaz. Ancak ArrayList gibi genelleyici olmayan veri kaynaklarında tipin açıkça bildirilmesi gerekir. Aşağıdaki kodu inceleyin.
var sorgu = from Ogrenci ogr in arrayList where ogr.Yazili[0] >= 55 select ogr;
Burada “from” ifadesinden sonra gelen “Ogrenci” ifadesi “ogr” isimli seri değişkeninin tipini bildirmektedir.
LINQ konusunda daha fazla bilgi için LINQ Örnekleri sayfamızı inceleyebilirsiniz.