Burak Selim Senyurt(MVP)
Matematik Mühendisi bir .NET Severin Yazıları...

C# 4.0 - ExpandoObject

Çarşamba, 21 Ekim 2009 16:14 by bsenyurt

Merhaba Arkadaşlar,

Bildiğiniz üzere .Net Framework 4.0 ile birlikte gelmesi muhtemel en köklü yenilikler arasında Dynamic Language Runtime alt yapısı yer almaktadır. Bu anlamda daha önceden dynamic anahtar kelimesini inceleyerek tiplerin dinamik olarak oluşturulup kullanılmasını kavramaya çalışmıştık. Bu yazımızda nasıl bir yenilikten bahsedeceğimizi anlatabilmek için öncelikle aşağıdaki kod parçasına odaklanmanızı istiyorum.

Not : Örnek henüz yayınlanmış olan Visual Studio 2010 Ultimate Beta 2 sürümü üzerinde geliştirilmiş bir Console uygulamasıdır.

Console.WriteLine("İlk Bakış\n");

dynamic employee = new ExpandoObject();

employee.Name = "Burak";
employee.Salary = 1000.23F;
employee.Birth = new DateTime(1976, 12, 4);
employee.WorkingArea = new ExpandoObject();
employee.WorkingArea.City = "Istanbul";
employee.WorkingArea.Degree = 1;
employee.WorkingArea.CustomerCount = 190;

Console.WriteLine(String.Format("\tName:{0} City:{1} Degree:({2})", employee.Name, employee.WorkingArea.City, employee.WorkingArea.Degree));

Şimdi bu kod parçası ile ilişkili olara bir kaç ipucu vermek istiyorum;

  1. Öncelikli olarak uygulamamızda Name,Salary ve diğer özelliklere sahip Employee gibi ismi olan herhangibir tip(type) tanımı bulunmamaktadır Wink
  2. İkinci kodu yazdığımız sırada employee isimli değişkene herhangi bir özellik eklemek istediğimizde aşağıdaki bilgi penceresi ile karşılaşırız.

Ahaaaa!!! Wink 

Sanıyorum olayın sizde farkına varmış durumdasınız. Aslında employee ismiyle tanımlamış olduğumuz değişkeni bir sınıf nesne örneği olarak inşa ettiğimizi düşünebiliriz. Ancak bunun için Emplyee tipini tanımlamış değiliz. Tamamen kodlama zamanında verdiğimiz bazı kararlar uygulamaktayız. Örneğin employee ismiyle tanımlanan tipin ve içeriğinin oluşturulması aşaması tamamen çalışma zamanına(Runtime) bırakılmış durumdadır. employee isimli değişkenin Name, Salary, Birth gibi özellikleri dışında başka bir ExpandoObject tipine işaret eden WorkingArea isimli bir özelliği daha bulunmaktadır. Buna göre WorkingArea özelliği de bir tip olarak düşünülebilir ki City, Degree, CustomerCount gibi özellikleri yer almaktadır.  Tabiki çalışma zamanında, özelliklere(Properties) atanan değerlere göre tiplerin ne olacağı da belirlenmektedir. Buna göre örneğin Name özelliğinin çalışma zamanında string tipinden olacağı açık ve nettir. Ancak WorkingArea özelliğinin kendiside aslında bir ExpandoObject tipidir. Kodun ilerleyen kısımlarına baktığımızda, employee değişkeninin özelliklerini kullanabildiğimizi de görebiliriz. Örnek kod parçamızın çalışma zamanı çıktısı aşağıdaki gibi olacaktır.

ExpandoObject tipinin aslında hangi amaçla kullanıldığını ilk etapta görmüş olduk. Peki daha neler yapabiliriz? Örneğin bir liste veya dizi oluşturulmasında, ExpandoObject nesnelerini kullanabilir miyiz? İşte cevabımız; 

Console.WriteLine("\nNesne Topluluğu\n");

dynamic personList = new List<dynamic>();

personList.Add(new ExpandoObject());
personList[0].Name = "Burak";
personList[0].Salary = 1000;

personList.Add(new ExpandoObject());
personList[1].Name = "Bill";
personList[1].Salary = 1250;

personList.Add(new ExpandoObject());
personList[2].Name = "Eva";
personList[2].Salary = 1050;

personList.Add(new ExpandoObject());
personList[3].Name = "Mayk";
personList[3].Salary = 900;

foreach (var person in personList)
    Console.WriteLine("\t{0}\t{1}", person.Name, person.Salary);

Bu sefer List<dynamic> tipinden bir koleksiyon oluşturulduğu ve koleksiyonun her bir nesnesinin ExpandoObject tipinden tanımlandığı görülmektedir. Buna göre örnek olarak Name ve Salary özellikleri olan nesnelerin dynamic listeye eklendiği görülebilir. Tabiki her öğe oluşturulma işleminden önce koleksiyona ExpandoObject tipinden bir nesne örneği eklenmesi gerekmektedir. Örneğin çalışma zamanındaki çıktısı aşağıdaki gibidir. 

Hatta istersek çalışma zamanında oluşturulacak bu nesneler üzerinde LINQ sorgularının çalıştırılmasını da sağlayabiliriz. Aşağıdaki kod parçasında yukarıdaki listenin bir LINQ ifadesi ile sorgulandığı görülmektedir.

Console.WriteLine("\nLINQ Kullanımı\n");

var result = from person in (personList as List<dynamic>)
    where person.Salary <= 1000
    select person;

foreach (var r in result)
    Console.WriteLine("\t{0}\t{1}", r.Name, r.Salary);

Bu kez dynamic liste içerisindeki nesnelerden Salary özelliği 1000 birime eşit ve az olanların çekilmesi için bir LINQ ifadesi kullanılmaktadır. Çalışma zamanındaki sonuç aşağıdaki gibi olacaktır.

Daha neler yapabilir? Acaba tanımladığımız ExpandoObject tipinden nesne için bir olay(Event) ekleyebilir miyiz? Hımmm...Undecided Bu oldukça ilginç olabilir. Aşağıdaki kod parçası ile bir deneyelim bakalım;

static void Main(string[] args)
{
   #region Event Kullanımı

   dynamic file = new ExpandoObject();

   file.Name = "DynamicOlmak.txt";
   file.Size = 1024;

   file.SizeChanged = null; // Bu satır olmadığı takdirde hata mesajı alınmakta.
   file.SizeChanged += new EventHandler(OnSizeChanged);
   EventHandler e = file.SizeChanged;
  
   Random rnd = new Random();
   file.Size += rnd.Next(1000, 10000);

   if (file.Size>=5000 && e != null)
      e(file, null);

   #endregion
}

public static void OnSizeChanged(object sender, EventArgs e)
{
   Console.WriteLine("Boyut değişti olayı tetiklendi");
}

Bu sefer file isimli değişkenin işaret ettiği tipe SizeChanged isimli bir olay(event) bildirimi yapılmaktadır. Event' ler bildiğiniz üzere delegate tipleri ile ilişkilidir. Örnekte basitlik açısından standart EventHandler temsilcisi kullanılmıştır ki dilerseniz farklı handler' ları veya kendi tanımladığınız delegate tiplerini de ele alabilirsiniz. Olayın file nesnesine eklenmesi sırasında birde olay metodunun işaret edildiği görülmektedir. Bildiğiniz üzere bu olay metodu, EventHandler temsilcisinin belirttiği parametrelere ve dönüş tipine sahip olmalıdır. Buna göre OnSizeChanged metodu olayın gerçekleşmesi sonrası devreye girecek olan fonksiyonumuzdur. Çok doğal olarak olayın bir sebepten tetikleniyor olması gerekir. Örnekte Size değerinin 5000' in üzerinde olması durumu değerlendirilmeye çalışılmıştır. Eğer böyle bir koşul oluşur ve e ile ifade edilen olay file nesnesine daha önceden ilave edilirse söz konu olay metodu tetiklenecektir. Aslında uygulamayı Debug ettiğimizde söz konusu olay metodu için MulticastDelegate kullanıldığını rahatlıkla görebiliriz.

Dikkat edileceği üzere dinamik olarak üretilen nesne içerisinde yer alan IDictionary koleksiyonunda Name, Size özellikleri ile birlikte MulticastDelegate tipinden tanımlanmış bir metod bildirimde yer almaktadır.

Peki ExpandoObject örneklerini metodlara parametre olarakta geçirebilir miyiz? Elbette Wink Aşağıdaki kod parçasını göz önüne alalım;

static dynamic file = new ExpandoObject();

static void Main(string[] args)
{
   #region Metoda Parametre Aktarımı

   file.Name = "DynamicOlmak.txt";
   file.Size = 1024;

   Console.WriteLine("Metod çağrısı öncesi dosya boyutu {0}",file.Size);
   Action(file);
   Console.WriteLine("Metod çağrısı sonrası dosya boyutu {0}", file.Size);

   #endregion
}

static void Action(dynamic obj)
{
   obj.Size += 456;
}

Burada file değişkeni sınıf seviyesinde tanımlanmıştır. Tabi örneği Console uygulamasında geliştirdiğimizden dynamic olarak tanımlanan ExpandoObject tipinin başına static anahtar kelimesininde gelmesi gerekmektedir. Bu kodu okurken biraz tuhaflığa neden olmaktadır. Undecided ExpandoObject örneği Action metoduna dynamic tipi üzerinden aktarılmaktadır. Dolayısıyla Action metodu içerisinde kullanılan obj değişkeninin file değişkeni olduğunu metod çağrısını yaptığımız yerde belirtmekteyiz. Bu nedenle Action metodu içerisine zaten file değişkeninin geldiğini biliyor olmalıyız. (Peki hangi tipin geldiğini anlayabilir miyiz? İşte size süper bir araştırma konusu Wink) Metod içerisinde sembolik olarak Size değerini arttırmaktayız. İşte çalışma zamanı sonucu; 

ExpandoObject tipi görüldüğü üzere dinamik olarak tiplerin oluşturulması, onlara üye tanımlanması(Özellik, Event gibi), özelliklerine değer atanması gibi işlemleri yapabilmemize olanak sağlamaktadır. Bu anlamda test senaryolarında stub veya moc nesneler için kullanılabilecek bir yenilik olarak düşünebiliriz belkide. Aklıma ilk gelen kullanım alanın bu olması, belkide The Art of Unit Testing kitabını okuyor olmamadan da kaynaklanabilir. Wink Aslında söz konusu yeniliği tanımak, kavramak ve gerçek hayat senaryolarındaki yerini anlamak için biraz daha araştırma yapmamız, çalışmamız gerektiği ortadadır. En azından benim...

Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

ConsoleApplication1.rar (25,51 kb)

C# 4.0 - Seçilebilen, İsimlendirilebilen Parametreler(Named and Optional Parameters), ref' i Görmezden Gelmek(Ommit Ref) ve PIA için Yenilikler

Pazartesi, 4 Mayıs 2009 22:46 by bsenyurt

Merhaba Arkadaşlar,

Bir önceki blog yazımızda C# 4.0 ile birlikte gelen önemli yeniliklerden birisi olan dynamic kavramına değinmeye çalışmıştık. Elbetteki C# 4.0 ile birlikte gelen başka yeniliklerde var. Bu yeniliklerde, diğerleri gibi belirli ihtiyaçlardan ortaya çıkmıştır. Öncelikli olarak bu ihtiyaçları ortaya koymaya çalışıyor olacağız. Bu nedenle PDC 2008'de dağıtılan Visual Studio 2010(PreBeta) sürümü ile yazdığım aşağıdaki kod parçasını bir süreliğine göz önüne alalım.

using System;
using System.Reflection;
using Word=Microsoft.Office.Interop.Word;

namespace NewFeatures2
{
    class Program
    {
        static void Main(string[] args)
        {
            Word.Application wrdApp = new Microsoft.Office.Interop.Word.Application();           
            wrdApp.Visible = true;
            object fileNamePath = @"C:\Yeni Ozellikler.docx";
            object missingValue = Missing.Value;

            wrdApp
                .Documents
                .Open(ref fileNamePath, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue);
            Console.WriteLine("Kapatmak için bir tuşa basınız");
            Console.ReadLine();
        }
    }
}

İlk olarak şunu belirtmek isterim; bu basit Console uygulamasında Microsoft.Office.Interop.Word assembly' ına ait bir referans yer almaktadır.

Program, sistemde yüklü olan Yeni Ozellikler.docx isimli bir Word dosyasını açmak için gerekli kodları içermektedir. Bu Word dosyasının açılması içinse, Microsoft.Office.Interop.Word assembly' ından yaralanılmaktadır. Bu assembly aslında Word ile konuşabilmemizi sağlayan COM API'sini sarmalayan(Wrap) bir yönetimli(Managed) kütüpanedir. Kod içerisinden, Word dökümanına erişebilmek için Application tipinden bir nesne oluşturulmaktadır. Uygulamanın görünürlüğü Visible özelliği ile set edildikten sonra ise, Open metodundan yararlanarak ilgili Word belgesinin açılması sağlanmaktadır. Ancak bu kod parçasında geliştirici açısından bazı zorluklar olduğu rahatlıkla gözlemlenebilir.

1- Open metoduna ait 16 adet parametrenin tamamının girilmesi zorunludur. Aşağıdaki şekilde durumun sıkıcılığı gözler önündedir.

NOT : Burada Juval Lowy' nin IDesign şirketinde kullandığı ve pek çok şirket tarafından benimsenen C# kodlama standartları aklıma geliyor. Buradaki belirttiği bir maddeyi hatırlıyorum. "Metodların argüman sayılarının 5 i geçmesinden kaçının. Eğer öyleyse struct tipini kullanın". Wink Burada bir COM API' sinin Wrap edilmiş kütüphanesi içerisindeki bir fonksiyonun parametre yapısının değiştirilemeyeceği fikri belleğimizi tamamıyla kaplayabilir. Tabi C# 3.0 sonrasında bir fırsat olabileceği de akıllara gelebilir. "Bir Extension metod yardımıyla Open metodu yerine bir alternatif geliştirebilir miyim? En azından parametre sayısını düşürmemizi kolaylaştıracak..." Bunu denemenizi öneririm.

2- Parametreler COM nesnesine iletildiğinden, dışarıdan yapılacak olan atamalarda object tipinin kullanılması gerekmektedir.

3- ref anahtar kelimesinin kullanılması zorunludur.

Gerçekte, Open metodu içerisinde işimize yarayan ve bizim için anlam ifade eden tek bir parametre yer almaktadır. O da açılmak istenen dosyanın adıdır. Diğer parametrelerinin hiçbirini kullanmadığımız halde yazmak zorunda olduğumuzu görüyoruz. Keşke sadece gerekli olanları yazsabilseydik; o zaman bu iş daha kolay olmazmıydı? Frown Nitekim buradaki Open metodu haricinde, çok daha fazla sayıda argüman ile çalışabilen COM fonksiyonellikleri söz konusu olabilir. Böyle bir durumda tam olarak tüm parametreleri yazma zorunluluğu bir kenara dursun, bunların bütünün ne işe yaradığınında bilinmesi gerekir.

Sanırım bu cümlelerden zaten nereye varmak istediğimi anlatabilmişimdir. .Net in gelecek nesillerinin en büyük hedeflerinden birisi dinamik dillere ait nesneler ile konuşabilmek ve bunu mümkün olduğunca kolaylaştırmaktır. Bu noktada COM API' leri gibi nesnelerinde kullanımı söz konusudur. Aynen yukarıda geliştirdiğimiz örnekte olduğu gibi. Bu nedenle C# 4.0 içerisinde seçimlik parametre kullanımına izin veren geliştirmeler yapılmıştır(Optional Parameters) Buna göre yukarıdaki kod parçasını C# 4.0 stilinde aşağıdaki gibi geliştirebiliriz.

Optional Parameters ile

using System;
using System.Reflection;
using Word=Microsoft.Office.Interop.Word;

namespace NewFeatures2
{
    class Program
    {
        static void Main(string[] args)
        {
            Word.Application wrdApp = new Microsoft.Office.Interop.Word.Application();           
            wrdApp.Visible = true;           
            wrdApp.Documents.Open(@"C:\Yeni Ozellikler.docx");
            Console.WriteLine("Kapatmak için bir tuşa basınız");
            Console.ReadLine();
        }
    }
}

Bu kod parçası çalıştığında da aynı sonucu alırız. Yine Word belgesi açılacak ve içeriği görüntülenecektir. Hem kodun okunurluğu kolaylaşmıştır, hem de kısalmıştır. Diğer taraftan parametre değerini aktarırken ref kullanılmadığına dikkat etmemiz gerekiyor.(Ommit ref özelliği) Üstelik object tipinden değişken ataması yerine doğrudan dosya adresininin içeriğini gönderebildiğimizede dikkat edelim.

Tabi ihtiyaçlar bitmek bilmiyor. Burada görüldüğü gibi gereksiz olan parametrelerin hiç biri bildirilmemiştir. Ayrıca ref anahtar kelimeside herhangibir şekilde kullanılmamıştır. Ancak arada başka bir parametrenin daha kullanılması gerekirse... Undecided Söz gelimi 3ncü parametre dosyanın yanlız okunabilir(ReadOnly) modda açılıp açılmayacağını belirtir. Optional Parameter tekniğini kullanırsak ikinci parametreyi atlamamız mümkün olmayacaktır. Acaba böyle bir vakada kodu yine istemediğimiz şekliyle aşağıdaki gibi geliştirmemiz mi gerekir?

using System;
using System.Reflection;
using Word=Microsoft.Office.Interop.Word;

namespace NewFeatures2
{
    class Program
    {
        static void Main(string[] args)
        {
            Word.Application wrdApp = new Microsoft.Office.Interop.Word.Application();           
            wrdApp.Visible = true;           
            object fileNamePath = @"C:\Yeni Ozellikler.docx";
            object missingValue = Missing.Value;
            object onlyRead = true;

            wrdApp
                .Documents
                .Open(ref fileNamePath, ref missingValue, ref onlyRead, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue);           

            Console.WriteLine("Kapatmak için bir tuşa basınız");
            Console.ReadLine();
        }
    }
}

Oysaki C# 4.0 bu gibi durumlar için isimlendirilmiş parametre(Named Parameters) kullanımını olanaklı kılmaktadır. Aşağıdaki şekilde görüldüğü gibi, intellisense' de bize yardımcı olmaktadır.

Dolayısıyla yukarıdaki kod parçasını aşağıdaki gibi geliştirebiliriz.

Named Parameters kullanımı ile

using System;
using System.Reflection;
using Word=Microsoft.Office.Interop.Word;

namespace NewFeatures2
{
    class Program
    {
        static void Main(string[] args)
        {
            Word.Application wrdApp = new Microsoft.Office.Interop.Word.Application();           
            wrdApp.Visible = true;
           
            wrdApp.Documents.Open(@"c:\Yeni Ozellikler.docx", ReadOnly: true);

            Console.WriteLine("Kapatmak için bir tuşa basınız");
            Console.ReadLine();
        }
    }
}

Son olarak Platform Interop Assembly(PIA) ile ilgili gelen yeniliklerden birisine değinmek istiyorum. Normal şartlarda Visual Studio 2010 öncesinde bir COM API' sini uygulamaya referans ettiğimizde, sarmalanan kütüphanenin özelliklerinde aşağıdaki şekilde görülen Embed Interop Types isimli bir kriter olmadığı bilinmektedir.

Oysaki Visual Studio 2010 ile birlikte bu özellikte gelmektedir. Bu tabiki sadece C# 4.0 diline bağlanacak bir yetenek olarak düşünülmemelidir.

Peki ne işe yarar? Eğer yukarıda geliştirdiğimiz C# 4.0 örneğinin ildasm(Intermediate Language DisAsseMbler) çıktısına bakacak olursak aşağıdaki durum ile karşılaşırız.

 

Göze çarpan özel bir nokta yer almamaktadır. Ancak Microsoft.Office.Interop.Word assembly' ının özelliklerinde yer alan Embed Interop Types seçeneğini true olarak değiştirir ve söz konusu uygulamanın IL çıktısına tekrardan bakarsak aşağıdak sonuçlarla karşılaşırız.

Görüldüğü gibi API içerisinde yer alan tipler, .Net programı içerisine birer tip olarak gömülmüştür. Aslında bu yenilik, PIA' ların, geliştirilen asıl uygulama içerisine tip bazında gömülerekten taşınabilmelerini kolaylaştırıcı bir özellik olarak görülebilir. Bu konudaki araştırmalarıma devam ediyorum. Yeni bilgiler kazandıkça sizlerle paylaşmaya devam ediyor olacağım.

Böylece geldik bir yazımızın daha sonuna. Bu yazımızda sizlere C# 4.0 ile birlikte gelen bir kaç yeniliği aktarmaya çalıştım. Sonuç olarak bu yeniliklerin özellikle dynamic tiplerin kullanımı kolaylaştırmak üzere getirildiğini söyleyebiliriz.

Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Tags:  
Categories:   C# 4.0
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

C# 4.0 - Dynamic Olmak

Cuma, 1 Mayıs 2009 00:02 by bsenyurt

Merhaba Arkadaşlar,

Uzun bir süredir (son bir senelik zaman dilimi içerisinde) C# 4.0 ile birlike gelen yeniliklerden haberdarız. Şöyle bir kaç sene öncesini hatırlıyorum da...Cool Visual Studio 2005, Whidbey kod adı ile yayınlanmış ve C# 2.0 ile birlikte gelen pek çok yenilik olmuştu. Ancak bunlar içerisinde belkide en önemli olanı, CLR(Common Language Runtime) çekirdiğinde değiştirilme yapılmasını da zorunlu kılan generic mimari kavramıydı. Tabiki generic dışında gelen, yield anahtar kelimesi, isimsiz metodlar(anonymous methods), static sınıflar ve diğerleride önemli gelişmelerdi. Zaman ilerledi ve C# 3.0 ile birlikte bu kez hayatımıza, generic modelinden daha fazla etki yapan LINQ(Language INtegrated Query) girdi. Bir geliştirici olarak her zaman için yeniliklere açık olmamız ve yakalayabildiğimiz ölçüde takip etmemiz gerektiğini düşünüyorum. Bu bir geliştirici için neredeyse bir yaşam tarzı. Dolayısıyla artık C# 4.0 üzerinde konuşmanın zamanı geldide geçiyor.

C# 4.0 ile birlikte gelen yeniliklerin daha çok dinamik çalışma zamanını(Dynamic Language Runtime-DLR) kullanan diller üzerinde odaklanmış durumda olduğunu söyleyebiliriz. Peki bu ne anlama geliyor? DLR tarafını ilgilendiren dillere ait nesneler ile daha kolay konuşulması olarak küçük bir sebep belirtebiliriz. Bu nedenle C# 4.0 ile birlikte gelen önemli yeniliklerden birisi olan dynamic anahtar kelimesi sayesinde, Python, Ruby veya Javascript ile üretilen nesnelerin C# 4.0 tarafında late-binding ile ele alınması mümkün. Hatta var olan .Net nesnelerinin reflection kullanılmadan ele alınması veya COM objelerine ait üyelerin çağırılmasında bu anahtar kelimeyi kullanabiliyoruz. Aslında C#' ın 2.0, 3.0 versiyonunda gelen yenilikler nasıl ki belirli ihtiyaçlar nedeni ile ortaya çıkmışsa, C# 4.0 ile gelen yenilikleride bu anlamda düşünmemiz ve araştırmamız gerekiyor.

Bu yazımda sizlerle dynamic kelimesi ile ilgili olan araştırmalarım sonucu elde ettiğim bilgileri paylaşıyor olacağım. İşe ilk olarak aşağıdaki şekilde görülen yapıya sahip olduğumuzu düşünerek başlayacağız.

Şimdi bu yapıyı kısaca açıklayalım. Commands isimli sınıf kütüphanesi(Class Library) IGraphic arayüzünü(Interface) uygulayan Circle ve Rectangle isimli sınıflara sahiptir.

IGraphic arayüzü

namespace Commands
{
    public interface IGraphic
    {
        void Draw();
    }
}

Circle sınıfı

using System;

namespace Commands
{
    public class Circle
        :IGraphic
    {
        #region IGraphic Members

        public void Draw()
        {
            Console.WriteLine("Circle...");
        }

        #endregion
    }
}

Rectangle Sınıfı

using System;

namespace Commands
{
    public class Rectangle
        :IGraphic
    {
        #region IGraphic Members

        public void Draw()
        {
            Console.WriteLine("Rectangle...");
        }

        #endregion
    }
}

Console Application tipinden olan uygulamamız, Commands isimli sınıf kütüphanesini referans etmekte olup başlangıçta aşağıdaki kod içeriğine sahiptir.

using Commands;

namespace CSharp4Features
{
    class Program
    {
        static void Draw<T>(T graphObject)
            where T : IGraphic
        {
            graphObject.Draw();
        }
        static void Main(string[] args)
        {
            #region Başlangıçtaki durumumuz

            Draw<Circle>(new Circle());
            Draw<Rectangle>(new Rectangle());

            #endregion
        }
    }
}

Uygulamayı çalıştırdığımızda aşağıdaki sonucu alırız.

Bu kod parçasında dikkat edilmesi gereken önemli noktalardan birisi, generic Draw<T> metodudur. Burada yer alan generic T tipine IGraphic arayüzünden türeme koşulu getirilmiştir. Bu sebepten dolayı, IGraphic arayüzünü uygulayan tüm tiplere ait Draw metounu çağırmamızı sağlayan tek bir metod geliştirmiş oluyoruz. Dolayısıyla tek yapılması gereken, Draw<T> metodunun kullanıldığı yerde, doğru tipe ait(bu örnek için Rectangle veya Circle) nesne örneğini parametre olarak aktarmaktır.

Şimdi burada, bizi dynamic kelimesine götürecek bir veya bir kaç sebep arayacağız.  İşte bir kaç blog içerisinde yakaladığım ortak soru geliyor...Ya Console uygulaması, IGraphic arayüzüne erişemiyor olsaydı. Bunu ayarlamak son derece kolay. Tek yapmamız gereken IGraphic arayüzünün public olan erişim belirleyicisini kaldırmak.(Bir başka deyişle internal'a çekmek) Bu durumda Draw<T> metodumuz için derleme zamanı(Compile Time) hatası alınacaktır. Peki ne yapılabilir? Reflection tekniklerinden yararlanarak ilgili tipin Draw metodunun çağırılması sağlanabilir. Yani kodu aşağıdaki hale getirebiliriz.

using Commands;
using System.Reflection;

namespace CSharp4Features
{
    class Program
    {
        static void Draw<T>(T graphObject)
        {
            MethodInfo methodInfo = typeof(T).GetMethod("Draw");
            if (methodInfo == null)
            {
                System.Console.WriteLine("Method bulunamadı");
            }
            methodInfo.Invoke(graphObject, new object[0]);
        }

        static void Main(string[] args)
        {
            Draw<Circle>(new Circle());
            Draw<Rectangle>(new Rectangle());
        }
    }
}

İlk olarak typeof metodu ile T tipi elde edilmekte ve Draw isimli metod istenerek MethodInfo tipinden bir referansa aktarılmaktadır. Bilindiği üzere reflection mimarisinde, çalışma zamanında tipler ve üyelerine ait bilgiler elde edilmekte ve istenirse üyelerin yürütülmesi(örneğin metodların çağırılması) sağlanabilmektedir. Bu nedenle ilk olarak T tipinin çalışma zamanı referansı üzerinden Draw metodu elde edilmeye çalışılır. Sonrasında ise eğer MethodInfo referansı null değilse Invoke fonksiyonuna gerekli parametreler gönderilerek Draw metodunun icra edilmesi sağlanır. (Tabiki çalışma zamanında gelen T nesne örneğine ait olan Draw metodunun) Uygulamayı bu haliyle çalıştırdığımızda yine aynı sonuçları alırız.

Ancak tabiki metodun bu yeni halinde tip güvenliğinden(type-safety) bahsetmemiz mümkün değildir. T için herhangibir tip kullanılabilir.

Peki dynamic anahtar kelimesi burada nasıl bir yaklaşım sunmaktadır. İşte aynı metodun C# 4.0 için dynamic anahtar kelimesi ile yazılmış hali.

using Commands;
using System.Reflection;

namespace CSharp4Features
{
    class Program
    {    
        static void Draw<T>(T graphObject)
        {
            dynamic obj = graphObject;
            obj.Draw();
        }

        static void Main(string[] args)
        {
            Draw<Circle>(new Circle());
            Draw<Rectangle>(new Rectangle());
        }
    }
}

Bu seferde aynı çıktıyı alırız. Tabi burada dikkat edilmesi gereken bir kaç nokta vardır ve kodun kısalmış olması bunlardan birisi değildir Wink Öncelikli olarak Draw metodu, söz konusu Circle veya Rectangle nesne örneklerine çalışma zamanında bağlanmaktadır. Bu zaten bizim reflection tekniği ile yapmakta olduğumuz bir işlemdir. Diğer yandan .Net Reflector aracı yardımıyla üretilen uygulama koduna bakıldığında söz konusu metod için aşağıdaki IL çıktısının oluşturulduğunu görebiliriz.

private static void Draw<T>(T graphObject)
{
    object obj = graphObject;
    if (<Draw>o__SiteContainer0<T>.<>p__Site1 == null)
    {
        <Draw>o__SiteContainer0<T>.<>p__Site1 = CallSite<Action<CallSite, object>>.Create(new CSharpInvokeMemberBinder(CSharpCallFlags.None, "Draw", typeof(Program), null, new CSharpArgumentInfo[] { new CSharpArgumentInfo(CSharpArgumentInfoFlags.None, null) }));
    }
    <Draw>o__SiteContainer0<T>.<>p__Site1.Target(<Draw>o__SiteContainer0<T>.<>p__Site1, obj);
}

Görüldüğü gibi Draw<T> metodu içerisinde <Draw>o_SiteContainer0<T> isimli generic bir tipin kullanıldığını ve bununda IL(IntermediateLanguage) tarafına eklendiğini görmekteyiz. Bir başka deyişle derleme işleminden sonra yine reflection kullanılan kod parçaları içeriye dahil edilerek, Circle veya Rectangle tiplerinden olan nesnelerin Draw metodunun çağırılması sağlanmış oldu.

NOT : Burada dikkat edilmesi gereken önemli bir noktada şudur. Eğer Draw<T> metoduna, Circle ve Rectangle dışında bir tip atarsak(özellikle Draw metodu olmayan) bu durumda RuntimeBinderException tipinden bir istisna alırız.

Tabiki bu kısım ve detaylarını daha iyi kavramak için belki biraz daha zamana ihtiyacımız olacak. Ancak bu anahtar kelimenin tek kullanım şeklinin, reflection ile elde edilen tiplere ait üyelerin çağırılmasını kolaylaştırmak olmadığınıda belirtmek isterim. Öyleki, dynamic kelimesi ile COM objelerinin ve bu sayede unmanaged API' lerin dinamik olarak ele alınması mümkün olabilir. Hatta, JSON formatına sahip bir nesnenin dynamic kelimesi ile kolayca ele alınabileceğini söyleyebiliriz. Bu noktada Office API' sine ait nesnelerin dynamic kelimesi ile son derece etkili ve kolay kullanılabildiğini de belirtmek isterim. Üstelik işin içerisine yine C# 4.0 ile gelen opsiyonel ve isimlendirilmiş parametreler(Optional and Named Parameters) adlı yeniliklerinde girdiğini söyleyebilirim. Bunu bir sonraki blog yazımda ele almaya çalışacağım.

Özet olarak artık C# programlama dilinin, dinamik olarak türlendirilmiş tiplere ait nesnelerle daha kolay konuşabildiğini söyleyebiliriz.

Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

NOT: Örnekler 2008 PDC'de yayımlanmış olan Visual Studio 2010 PreBeta sürümü üzerinden geliştirilmiştir.

Tags:  
Categories:   C# 4.0
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (4) | Comment RSSRSS comment feed
Bookmark and Share