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

Webiner - C# 4.0 - Yenilikler [Beta 2]

Perşembe, 7 Ocak 2010 02:00 by bsenyurt

Merhaba Arkadaşlar,

Çok eskiden bir Delphi programıcısydım. Açıkçası o dönemlerde Delphi geliştirme ortamının hayranı olduğumu itiraf etmeliyim. Delphi 1.0 ile başlayan profesyonel iş yaşantımda ilk geliştirmeye çalıştığım ve neredeyse para kazanmak üzere olduğum programı Üniversiteden çok değerli bir sınıf arkadaşım(Orkun Şentürk) ile birlikte yazamamıştık. Undecided Arkadaşımın motorsiklet satın aldığı bir mağazaya indirim karşılığında basit bit stok takip programı yazacaktık. Ne varki arkadaşım bir gün motorsikletinin sinyal lambasını kırdı ve 100 Mark değerinde olan lambayı karşılamak için programa yeni özellikler dahil ediliverdi.Laughing

O zamanlar Delphi 2.0 sürümüne henüz kavuşamamıştık ve Delphi 1.0 sürümünde veritabanı işlemleri ile ilişkili önemli sıkıntılar vardı. Sonuçta programı yazamadık. Onun yerine bir muhasebe programı satın almalarını(MSDOS tabanlı) önerdik. Tabi yine aynı arkadaşımla şeytanın bacağını bir sonraki projemizde kırdık ve yine Delphi 2.0 ile başarılı bir program yazıp satmayı başardık.

Ancak profesyonel yazılım geliştirme yaşantım zaman içerisinde Anders Heijslberg(C# ın babası diyebiliriz)' in Microsoft takımına geçmesiyle tamamen yön değiştirdi. Anders' in Microsoft'a geçişinden kısa bir süre sonra .Net Framework platformunu ve C# programlama dilini duyduk. Akabinde tabiki Visual Studio.Net geliştirme ortamı. C# programlama dilini daha görür görmez benimsemiş ve etkilenmiştim. Zaman ilerledikçe C# programlama dilide önemli gelişmeler gösterdi tabiki. Çünkü yazılımların ihtiyaçları, gereksinimleri günden güne artıyor ve programlama dillerini kendilerine uymaları konusunda zorluyordu.

Derken .Net Framework 2.0 köklü bir değişikliğin eklendiğini gördük. Generic mimari. Köklüydü çünkü CLR(Common Language Runtime) üzerinde değişikliklerin yapılmasını gerektiriyordu. Çok doğal olaraktan dil içerisinde buna destek verecek yenilikler yapıldığına ve dile özgü başka ilavelerin getirildiğine tanık olduk. Anonymous metodlar, yield anahtar kelimesi, static sınıflar vb...Tabiki yapılan yeniliklerin belirli amaçları olduğu kesindi. Bu amaçların iyice belirgineştiği yer ise .Net Framework 3.5 sürümüydü. Bu vesileyle Language INtegrated Query(LINQ) hedefli olarak gelen yenilikler ve bunun için dilde yapılan geliştirmelerle karşılaştık. Extension metodlar, Anonymous tipler, lambda(=>) operatörü, Auto Property' ler, Object Initializer' lar, partial event' ler vb...

Uzun bir süredirde C# 4.0 dili birlikte gelmesi muhtemel yenilikler söz konusu. Halen daha Beta 2 aşamasındayız ancak yakın zamanda RC versiyonu ile birlikte nihai sürüme ulaşacağımızı biliyoruz. (Her ne kadar zamanında bazı kişiler fake olarak C# 4.0 yeniliklerini bloglarında duyurup yayınlasalarda gerçekleri yansıtmıyorlardı) Özellikle Dinamik Dil Çalışma Zamanı(Dynmaic Language Runtime) ile olan etkileşimin arttırılmasını hedef alan yeni dil özelliklerini NedirTV?com adına düzenlenen webinerimizde detayları ile inceledik. Umarım sizler için yararlı olmuştur.

Süre 58:52

C# 4.0 - Invariance, Covariance, Contravariance ???

Salı, 22 Aralık 2009 16:15 by bsenyurt

Merhaba Arkadaşlar,

Bundan yıllar önce(aslında 2005 yılında...Çok eski bir tarih gibi görünmese de yazılım dünyası için çok çok uzun zaman önce anlamına gelmekte.) daha genç bir makale yazarıyken C# 2.0 delegate tiplerinde co-variance, contra-variance durumlarını incelemeye çalışmıştım. Kişisel görüşüme göre, anlaşılmasından ziyade iyi bir şekilde analiz edilerek anlatılması çok zor olan bir konu Co-Variance, Contra-Variance. Üstelik bu kavramların çıkış noktasında yer alan Variant, Invariant tip kavramları  düşünüldüğünde konuyu anlamak için epey bir çaba sarf etmemiz gerekebiliyor. Hatta anlayamadığımız durumlarda neredeyse bulunduğumuz duruma isyan eder bir hale gelebiliyoruz. Bu tip zor konularda benim öğrenmek üzerine uyguladığım strateji aslında pek çoğumuzun da uyguladığı bir yöntem. Önce sorunu örnekler ile anlamaya çalışmak, getirilen çözümü görmek ve en son olarak tanımlamaları yapmak. Bu önce kavram tanımlaması, sonra örnek uygulamanın yapılmasından ziyade daha etkili bir öğrenme şeklidir diye düşünüyorum. Öyleyse vakit kaybetmeden analizimize başlayalım.

.Net' in başından beri...

Aslında .Net' in ilk duyurulduğu ve C#, Vb.Net gibi nesne yönelimli yönetimli dillerin(Managed Languages) dünyaya geldiği anlardan bu yana kalıtımsal ilişkide olan tipler arasında bazı referans geçişlerinin yapıldığını bilmekteyiz. Burada tiplerin polimorfik özellikte olabilmelerinin de payı büyük. Bu sebepten .Net Framework' ün tüm sürümlerinde aşağıdaki gibi bir kod parçası olası.

using System;

namespace Before
{
    class Program
    {
        static void Main(string[] args)
        {
            WriteString(35);
            WriteString("Burak Selim Şenyurt");
            WriteString(true);
            WriteString(new Album { AlbumID = 1, Title = "Chikenfoot" });
        }

        static void WriteString(object obj)
        {
            Console.WriteLine(obj.ToString());
        }       
    }
    class Album
    {
        public int AlbumID { get; set; }
        public string Title { get; set; }
    }   
}

Bu kod parçasında yer alan WriteString metodu tüm .Net tiplerinin atası olan object tipinden parametre almaktadır. Bu ata tip-alt tip ilişkisi nedeniyle metoda herhangibir .Net tipinin atanması söz konusudur. Üstelik herkes bir Object tipi olduğundan ToString metodunu uygulamakta veya uygulamasa bile object tipinin varsayılan ToString metodu çalıştırılabilmektedir. Nitekim Album tipi içerisinde ToString metodu ezilmemiş olmasına rağmen Object sınıfındaki varsayılan ToString metodunun çalıştırılması sağlanmıştır. (Bildiğiniz üzere ToString metodu object tipi içerisinde virtual olarak tanımlanmıştır ve alt tiplerde ezildiği takdirde objcet nesne referansı üzerinden ezilen versiyonunun çalıştırılması söz konusudur.)

Yolumuza devam edelim ve bu sefer aşağıdaki kod parçasını göz önüne alalım.

static void Main(string[] args)
{
   object albm = CreateAlbum(2, "Is There a Love in space[Joe Satriani]");
}
static Album CreateAlbum(int albumId,string title)
{
   return new Album{AlbumID=albumId,Title=title};
}

Bu kod parçasında yer alan CreateAlbum metodu Album tipinden bir değer döndürmektedir. Main metodu içerisinde ise CreateAlbum çağrısı sonucunun object tipine atanması söz konusudur. Her iki kod parçasınında çalışma zamanında veya derleme zamanında bir hata üretmesini beklemeyiz. Şimdi koltuklarınıza yaslanın ve takip eden paragrafı okuyun...

İlk kod parçasının çalışması doğaldır nitekim .Net' in tüm sürümlerinde parametreler Covariant tiptedir. Diğer yandan ikinci kod parçasının da çalışması doğaldır çünkü dönüş tipleride Contravariant' tır.

Bir şey anladınız mı? Açıkçası ben halen daha durumu tam olarak netleştiremediğimizi düşünüyorum. Öyleyse...

Generic mimariden önceki koleksiyonlarda durum...

İşin içerisine object tipinden değerler ile çalışan generic mimari öncesi koleksiyonlar girdiğinde, Covariance veya Contravariance olmanın artık güvenli olup olmadığından söz edilmeye başlanmaktadır. Güvenlik tip bazındadır. Şimdi bu durumu anlamaya çalışarak devam edelim.

ArrayList albumList = new ArrayList();
albumList.Add(new Album { AlbumID = 1, Title = "Chikenfoot" });
albumList.Add(new Album { AlbumID = 2, Title = "Is There a Love in space[Joe Satriani]" });
albumList.Add(new Album { AlbumID = 3, Title = "Big Blue Ball [Peter Gabriel]" });

ArrayList gibi koleksiyonların object tipi ile çalışmalarının sebebi herhangibir tip için koleksiyon bazlı özelliklerin kullanılabilmesini sağlamaktır (Tabi bu, tip güvensiz-unsafe bir durumu oluşturmuş ve sonrasında generic mimari getirilmiştir). Diğer yandan object ile çalışmaları nedeniyle, herhangibir .Net tipini bünyesinde barındırabilirler. Buna göre ilk örneğimizi ve bu son kod parçasını göz önüne alırsak koleksiyonların Covariance özelliğini sağladığını(yani Covariant tipte olduklarını) düşünebiliriz. Ancak buda tam olarak doğru değildir. Şimdi aşağıdaki kod parçasını göz önüne alalım.

using System;
using System.Collections;

namespace Before
{
    class Program
    {
        static void Main(string[] args)
        {
            ArrayList albumList = new ArrayList();
            albumList.Add(new Album { AlbumID = 1, Title = "Chikenfoot" });
            albumList.Add(new Album { AlbumID = 2, Title = "Is There a Love in space[Joe Satriani]" });
            albumList.Add(new Album { AlbumID = 3, Title = "Big Blue Ball [Peter Gabriel]" });
            albumList.Add("Reality Killed The Video Star [Robbie Willams]");

            WriteAlbumList(albumList);
        }

        static void WriteAlbumList(ArrayList albums)
        {
            foreach (Album albm in albums)
            {
                Console.WriteLine(albm.ToString());
            }
        }
    }
    class Album
    {
        public int AlbumID { get; set; }
        public string Title { get; set; }

        public override string ToString()
        {
            return String.Format("{0} {1}", AlbumID.ToString(), Title);
        }
    }   

Çok doğal olarak foreach döngüsü içerisinde albums koleksiyonu üzerinde dolaşılırken sadece Album tipleri ele alınmak istenmiştir. Ancak birisi kazayla albumList isimli koleksiyona string tipte bir değişken göndermiş ve yukarıdaki çalışma zamanı hatasının alınmasına neden olmuştur. Aman tanrımmmm!!! Sealed Şimdi koleksiyonlar için bahsedilen Covariant tipte oldukları gerçeği Un-Safe Covariant olarak düzeltilmelidir. Nitekim tip güvenliğinin garanti altına alınması mümkün olmamıştır. Hatta pek çok kaynak object tipi ile çalışan koleksiyonların aslında tamamen Invariant olduklarını ifade etmektedir. Kafamız gittikçe karışıyor değilmi. Öyleyse...

Peki ya diziler(Arrays)...

Aşağıdaki ekran görüntüsünde yer alan kod parçasını göz önüne alalım.

WriteAll metodu object tipinden bir dizi ile çalışmaktadır. Buna göre string tipinden bir dizinin bu metoda parametre olarak aktarılması mümkündür. Burada Covariance olma durumu söz konusudur. Elbette güvensiz olan versiyonu. Çünkü WriteAll metodu içerisinde bir tip güvenliği yoktur. Diğer yandan ilginç olan durum int tipinden bir diziyi göndermek istediğimizde ortaya çıkmaktadır. Böyle bir durumda derleme zamanı hatası alınacaktır. Int tipi değer tipi olduğundan object gibi bir referans türüne atanması mümkün değildir. İşte bu noktada object tipinden dizinin Invariance özellik gösterdiğini söyleyebiliriz.

Buna göre; Diziler bazı durumlarda Invariant bazı durumlarda ise Covariant tip olarak görülebilirler. Ancak Covariant olsalar dahi tip güvensiz olacaklardır(Unsafe). Diğer yandan değer türlü(Value Type) diziler her zaman için Invariantce özellik gösterir.

Generic koleksiyonlar...

Gelelim tip güvenli(Type Safe) olan generic koleksiyonlara. Generic mimari .Net içerisinde bir devrim yaratmış ve major versiyonlamaya gidilmesine neden olmuştur. Generic mimari sayesinde örneğin koleksiyonların sadece söylenen tiple çalışabileceği daha kodlama zamanındayken belirtilebilmekte ve böylece çalışma zamanında tip güvenliğinin aşılması engellenmektedir. Peki ya aşağıdaki kod parçasını göz önüne aldığımızda;

using System;
using System.Collections;
using System.Collections.Generic;

namespace Before
{
    class Program
    {
        static void Main(string[] args)
        {
            WriteAll(new List<Album>{
                new Album { AlbumID = 1, Title = "Chikenfoot" },
                new Album { AlbumID = 2, Title = "Is There a Love in space[Joe Satriani]" }
            }
            );

        }
        static void WriteAll(IEnumerable<object> parameters)
        {
            foreach (object parameter in parameters)
            {
                Console.WriteLine(parameter.ToString());
            }
        }
    }
    class Album
    {
        public int AlbumID { get; set; }
        public string Title { get; set; }

        public override string ToString()
        {
            return String.Format("{0} {1}", AlbumID.ToString(), Title);
        }
    }   
}

Burada WriteAll metodu IEnumerable<object> tipinden bir parametre almaktadır. Buna göre tüm T generic tiplerinin object tipinden türeyeceği düşünüldüğünde herhangibir sorun olmayacağı sonucuna varılabilir (En azından kafamızdaki compiler bu kodu sorunsuz olarak derleyecektir. İlk etapta...Wink) Yani Covariance olma durumu söz konusudur diyebiliriz...Mi acaba? İşte derleme zamanının bize vereceği cevap...

Görüldüğü üzere Album tipi ile çalışan koleksiyonun object tipine dönüştürülemeyeceğini belirten bir hata mesajı ile karşı karşıyayız. Buna göre generic koleksiyonların aslında Invariant tipte olduklarını ifade edebiliyoruz. Bu nedenle generic koleksiyonlar ne Covariance nede Contravariance özellik göstermektedir. Aslında generic koleksiyonlarda özel bir durum olarak T tipleri için türetmenin söz konusu olmadığını söyleyebiliriz. Bu sebepten IEnumerable<Album> ün IEnumberable<object> tarafından taşınması söz konusu olmamaktadır. Birde aşağıdaki kod parçasını göz önüne alalım;

using System;
using System.Collections;
using System.Collections.Generic;

namespace Before
{
    class Program
    {
        static void Main(string[] args)
        {
            IEnumerable<object> albums = GetAlbums();
        }

        static IEnumerable<Album> GetAlbums()
        {
            return new List<Album>
            {
                new Album { AlbumID = 1, Title = "Chikenfoot" },
                new Album { AlbumID = 2, Title = "Is There a Love in space[Joe Satriani]" }
            };
        }
    }
    class Album
    {
        public int AlbumID { get; set; }
        public string Title { get; set; }

        public override string ToString()
        {
            return String.Format("{0} {1}", AlbumID.ToString(), Title);
        }
    }
   
}

GetAlbums metodu IEnumerable<Album> tipinden bir referans döndürmektedir. Album, Object' in alt tipidir. Buna göre GetAlbums metodunun sonucunun IEnumerable<object> tipinden bir arayüze atanabiliyor olması düşünülebilir. Oysaki derleme zamanında aşağıdaki hata mesajı alınacaktır.

Görüldüğü üzere bir dönüştürme hatası alınmaktadır.

Şimdi buraya kadar anlattıklarımızı gözden geçirecek olursak belki şu cümleleri sarf edebiliriz.

  • Invariant tiplerin kullanıldığı yerlerde, belirtilen tipin birebir aynısının ele alınması gerekmektedir(.Net 4.0 öncesi generic koleksiyonlar)
  • Covaraint tiplerin kullanılabildiği yerlerde, alt tipten olan değişkenlerin parametre olarak aktarılması mümkündür.
  • Covariant' lık için güvensiz olma(Unsafe) durumlarıda söz konusudur.(Object tipli koleksiyonlar ve diziler)
  • Contravariant' lığa göre ise dönüş tipinin metoddan dönen tipin üst tipiden bir nesne örneğine atanması mümkündür.
  • Covariant tipler için geçerli olan güvensiz tip sendromu doğal olarak Contravariant tipler içinde söz konusudur.

Peki .Net 4.0 sonrasında...

.Net 4.0 versiyonunda Generic koleksiyonların katı olan tip kavramı bozularak Covariant ve Contravariant olmalarına izin verildiğini söyleyebiliriz. Ancak burada önemli bir fark olduğundan bahsetmemiz gerekiyor. Generic koleksiyonların Covariant ve Contravariant olarak çalışabilmeleri sağlanırken bunun güvenli olaraktan(Type Safe) gerçekleştirilebilmesi sağlanmış. Bu noktada örneğin object tipinden olan koleksiyonların covariance ve contravariance davranışlarından ayrıldığını ifade edebiliriz. Peki bu yeni kabiliyetler nasıl aktarılmış?

İşte IEnumerable<T> arayüzünün .Net 4.0' da tanımlanış şekli.

out T tanımlaması mutlaka dikkatiniz çekmiştir. Bu durum output safe olarak adlandırılmaktadır. Şimdi başka bir tipi göz önüne alalım.

Bu sefer in T kullanımı dikkati çekmektedir. Bu durum ise Input Safe olarak adlandırılmaktadır. Her iki durumda alt tarafta öyle ele alınmaktadırki generic koleksiyonlarda Covariant ve Contravariant' lığın tip güvenli olarak ele alınması mümkün olmaktadır. Sonuç olarak kafamız karışsada biraz .Net 4.0 ile birlikte generic koleksiyonların tip güvenli olaraktan Covariant ve Contravariant özellik gösterebilmelerinin mümkün hale geldiğini söyleyebiliriz. Bununla birlikte out T ve in T kullanımlarının şu an için sadece generic koleksiyonlar ve temsilcilerde(delegate) söz konusu olduğunu belirtelim. Dolayısıyla generic temsilcilerinde tip güvenli Covariant veya Contravariant olarak ele alınmaları mümkündür. Buna göre out T ve in T için şu ifadeleri kullanabiliriz.

  • out T Covariant tip kullanımını sağlamaktadır. Buna göre örneğin IEnumerable<object> tipte parametre alan bir metoda, IEnumerable<Product> gibi bir referansın atanabilmesi mümkündür.
  • in T Contravariant tip kullanımı sağlamaktadır. Buna göre örneğin IEnumberable<string> dönen bir metodun sonucunun IEnumerable<Object> tipine atanması mümkündür.

Dolayısıyla aşağıdaki örnek kod parçası sorunsuz olarak derlenecek ve çalışacaktır.

using System.Collections.Generic;

namespace NowInNet4
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Product> products = new List<Product>
            {
                new Product{ProductId=1,Name="Americano Coffee",ListPrice=10},
                new Product{ProductId=2,Name="English Royal Tea",ListPrice=12}               
            };

            Process(products);

            IEnumerable<object> allProducts = GetProducts();
        }

        static IEnumerable<Product> GetProducts()
        {
            return new List<Product>
            {
                new Product{ProductId=1,Name="Americano Coffee",ListPrice=10},
                new Product{ProductId=2,Name="English Royal Tea",ListPrice=12}               
            };
        }

        static void Process(IEnumerable<object> parameters)
        {
            // Bir takım işlemler
        }
    }
    class Product
    {
        public int ProductId { get; set; }
        public string Name { get; set; }
        public decimal ListPrice { get; set; }
    }
}

Ben en azından biraz olsun anlamış durumdayım. Umarım sizlerede en iyi şekilde aktarabilmişimdir. Tekrardan görüşünceye dekhepinize mutlu günler dilerim.

CoContraVariance.rar (38,87 kb)

C# 4.0 ile Code Contracts

Cuma, 18 Aralık 2009 14:40 by bsenyurt

Merhaba Arkadaşlar,

Microsoft gibi dev yazılım firmalarının araştırma geliştirme ekipleri ve labarotuvar çalışmaları her zaman ilgimi çekmiştir. Herhalde pek çok yazılımcının hayalleri arasında bu tip firmalarda çalışmak ve yeni fikirleri ortaya atarak diğer yazılımcılara sunmak yer almaktadır. Microsoft' un DevLabs isimli portalında bu tip fikirlerin labarotuvar çalışmalarının yer aldığını görebilirsiniz. Örneğin son zamanların popüler konularından birisi olan observable koleksiyonları kullanarak asenkron(Asynchronous) ve olay güdümlü(Event Based) programlamayı kolaylaştıran Reactive Extensions, yeni başlayanlara yazılım anlatan Small Basic yada White Box testleri için geliştirilen Pex... Tabi daha pek çok labarotuvar çalışması yer almaktadır. Bunlardan biriside Code Contracts' tır.

Uzun süredir ilgimi çeken ama fırsat bulamadığım konulardan birisidir Code Contracts. Özellikle test süreçlerinde önem arz eden ve kodun çalışma zamanında veya kodlama zamanında varsayımsal bazı koşulları sağlayıp sağlamadığını tespit etmemizi sağlayan bir yenilik olarak düşünülebilir. Bu noktada kodun ön koşullu(Pre-Conditions) veya son koşullu(Post-Conditions) olarak test edilmesinin mümkün olduğunu söyleyebiliriz. Bu iki yeteneğe ek olarak bir nesnenin durumunun(State) beklendiği gibi olmasının kontrolüde Object Invariant özelliği sayesinde gerçekleştirilebilmektedir. 

Normal şartlarda Microsoft.Diagnostics.Contracts isim alanı(Namespace) altında yer alan tipler ile kod sözleşmelerinin kullanılması mümkündür. Bu isim alanı .Net Framework 4.0 Base Class Library içerisinde doğrudan gelmektedir. .Net Framework 4.0 öncesi sürümlerde kullanmak istediğimizdeyse Microsoft.Contracts.dll assembly' ının projeye referans edilmesi(örneğin XP işletim sisteminde C:\Program Files\Microsoft\Contracts\PublicAssemblies\v3.5 adresinde yer almaktadır) gerekmektedir. Kolay kullanım için yazıyı hazırladığım tarih itibariyle DevLabs' ten gerekli aracın(Tool) indirilerek kurulmasında yarar vardır. Code Contracts, Microsoft DevLabs içerisine dahil edilmiş önemli projelerden birisidir. Code Contracts' ın bloğun yazıldığı tarih itibariyle iki farklı versiyonu bulunmaktadır. Standart versiyon çalışma zamanı kontrollerini(Runtime Checking) yapabilmekteyken, Team System için üretilen versiyon static kontroller(Static Checking) yapabilmektedir. İlgili sürümler yine Visual Studio 2010 Beta 2 sürümü ile de çalışmaktadır.

Not : Eğer elinizde benim gibi Visual Studio 2010 Ultimate Beta 2 sürümü var ise, Team System Edition sürümünü kurabilirsiniz.

Kurulum işlemi sonrasında Visual Studio Ultimate 2010 Beta 2 üzerinde oluşturulan projelerin özelliklerine(Properties) aşağıdaki ekran görüntüsünde yer alan bir ara birimin dahil edildiğini görürüz.

,

Burada yer alan detaylı özellikleri zaman içerisinde öğreniyor olacağız. İlk etapta Static Checking kısmının sadece Team System destekli Visual Studio' lar üzerinde(Visual Studio 2008 içinde geçerlidir) etkinleştirilebildiğini belirtelim. Aslında bu kabiliyetlerin özellikle çalışma zamanında yapılan if...try...catch gibi kontrollerden ne gibi farkı olduğunu kavramamız oldukça önemlidir. Örneğin dökümantasyon avantajları bunlardan birisidir. Söz gelimi metod parametrelerinin gereklilikleri(Requirements), olası istisnalar(Exceptions) ve gerekli izinler(Permissions) için otomatik dökümantasyon üretimi sağlanabilmektedir. Diğer yandan Unit Test tarafına getirdiği avantajlardan da bahsedilebilir. Daha anlamılı Unit Test' lerin üretilebilmesi, özellikle her sözleşmenin(Contracts) bir analist gibi davranması ve çalıştırılan Test için pass/fail işaretlemesini sağlayabilmesi vb...Tabiki konuyu daha iyi kavrayabilmek adına bol bol örnek geliştirmekte yarar vardır. İşe çok basit bir Hello World uygulaması ile başlamakta yarar olacağı kanısındayım. Bu nedenle aşağıda yer alan Console uygulaması kodlarını yazdığımızı varsayalım.

using System.Diagnostics.Contracts;

namespace CodeContracts
{
    class Program
    {
        static void Main(string[] args)
        {
            ChinookContext context = new ChinookContext();       
            context.CreateAlbum(null, 1);
            Album result=context.CreateAlbum("The Best", 1);
            Contract.Ensures(result.Name.Length > 10, "Album nesnesinin oluşturulmasında albüm adının 10 karakterden fazla olması beklenir");
        }
    }

    class ChinookContext
    {
        public Album CreateAlbum(string albumName, int albumId)
        {
            Contract.Requires(!string.IsNullOrEmpty(albumName),"Album nesnesinin oluşturulması için Album adının null veya boş olmaması gerekir");
            Album albm=new Album {
                AlbumId=albumId
                ,Name=albumName };           
            return albm;
        }
    }

    class Album
    {
        public int AlbumId { get; set; }
        public string Name { get; set; }
    }
}

Bu örnek kod parçasında Album isimli sınıfa ait iki nesne örneği üretiminin gerçekleştirildiğini görmekteyiz. CreateAlbum isimli metod içerisinde Contract.Requires isimli bir static fonksiyon çağrısı olduğu hemen dikkatinizi çekmiş olmalıdır. Bu metod ile bir ön koşul(Pre-Condition) belirtilmektedir. Bu koşula göre CreateAlbum metoduna gelen albumName parametresinin değerinin null veya boş olmaması beklenmektedir. Diğer yandan Main metodunun son satırında da Contract.Ensures isimli bir metod çağrısı yer almaktadır. Bu çağrı ilede bir son koşul(Post-Condition) tanımlaması yapılmaktadır. Bu koşula göre CreateAlbum metodu ile üretilen ikinci Album nesne örneğinin Name özelliğinin karakter sayısının 10' un üzerinde olması istenmektedir.

Örneği bu haliyle çalıştırdığımızda hiç bir sorun olmadığını görürüz. Hımmm...Enteresan bir durum.Undecided Oysaki Requires veya Ensures metod çağrılarından en az birisine takılmamız gerekirdi. Aslında sorun henüz kod sözleşmelerinin çalışma zamanı(runtime) veya static olarak izlenmesi gerektiğini belirtmemiş olmamız. Bunun için Tool ile birlikte projeye eklenen Contracts sekmesindeki Runtime Checking kutusunu işaretlememiz yeterlidir.

Şimdi örneğimizi çalıştırdığımızda ilk olarak aşağıdaki mesaj kutusu ile karşılaştığımızı görürüz.

Dikkat edileceği üzere Album adının null geçilmesi nedeniyle bir uyarı mesajı üretilmiştir. Bir başka deyişle ön koşulun sağlanamadığı açık bir şekilde görülmektedir. Bu noktadan sonra hatayı görmezden gelip ilerleme şansımız vardır. Ignore düğmesine basarak devam ettiğimiz takdirde bu kez Post-Condition' a takıldığımızı görebiliriz.

Üretilen uyarı mesajına göre ikinci Album nesnesinin adının karaketer uzunluğunun istenildiği gibi olmadığı gözlemlenmektedir. Şimdi dilerseniz Object Invariant konusuna dair basit bir örnek geliştirmeye çalışarak devam edelim. Invariant özelliğini bir nesnenin istemci tarafından ele alındığı yerdeki durumunun(State) iyi olması ile alakalı bir yetenek şeklinde düşünebiliriz. Bir nesnenin durumunu içerdiği alanlar ifade ettiğinden, bu alanların beklenen şekilde olması iyi bir nesne(good object) olduğu anlamına da gelecektir. İşte örnek kod parçası.

using System.Diagnostics.Contracts;

namespace CodeContracts
{
    class Program
    {
        static void Main(string[] args)
        {
            Product bardak = new Product(1000,"Bardak",3.45);
        }
    }

    class Product
    {
        private int ProductId;
        private string Name;
        private double ListPrice;

        public Product(int pId,string pName,double pListPrice)
        {           
            ProductId = pId;
            Name = pName;
            ListPrice = pListPrice;
        }

        [ContractInvariantMethod]
        protected void InvariantCheck()
        {
            Contract.Invariant(this.ProductId > -1,"Ürün numarası pozitif değer olmalıdır");
            Contract.Invariant(this.Name.StartsWith("PRD-"),"Ürün adları PRD- ile başlamalı");
            Contract.Invariant(this.ListPrice != 0, "Liste fiyatı 0 olamaz");
        }
    }
}

Product isimli sınıfta ContractInvariantMethod niteliği(attribute) ile imzalanmış bir metod bulunmaktadır. Geriye değer döndürmeyen ve protected olarak işaretlenmiş bu metod içerisinde dikkat edileceği üzere Contract sınıfının static Invariant metoduna yapılan bazı çağrılar bulunmaktadır. Bu çağrılar içerisinde, üretilen Product nesnesinin durumunu simgeleyen alanların değerleri kontrol edilmektedir. Örnek çalıştırıldığında aşağıdaki mesaj ile karşılaşılacaktır.

Bu son derece doğaldır nitekim ürün adı Invariant çağrısında olduğu gibi PRD- ön eki ile başlamamaktadır. Dolayısıyla nesne örneği istenilen duruma sahip değildir.

Kişisel Not: Invariant kontrolünde dikkat edilmesi gereken iki durum vardır. Auto Property' ler kullanıldığında çalışma zamanında bir istisna mesajı alınacaktır. Bu istisna Contract.Invariant çağrılarının yapıldığı yerde gerçekleşecektir. Bunun sebebi Invariant çağrılarının null değer içeren özellikleri kontrol etmeye çalışmasıdır. Söz konusu durum varsayılan yapıcı metodlar kullanıldığında da nüksetmektedir. Söz gelimi yukarıdaki kod parçasında Product tipi için varsayılan yapıcı metod yazılıp buna göre bir Product nesnesi örneklendiğinde aşağıdaki çalışma zamanı istisna(Runtime Exception) mesajı ile karşılaşılır.

Name alanı string tipten olduğu için nesnenin ilk örneklenmesi sırasında varsayılan yapıcı metod(Default Constructor) tarafından null değer ile beslenir. Bunun sonucu olarakta Invariant metodu null değer üzerinde kontrol yapmaya çalışmaktadır.

Elbette Code Contracts konusu burada anlatıldığı kadar yalın ve sade değildir. Aksine dökümantasyonuna bakıldığında çok fazla kuralı olduğu görülmektedir. Özellikle Static Checking özelliği derleme işlemi sırasında bazı kod sözleşmelerinin kontrolü sağlamaktadır. Konuyu araştırdıkça ve öğrendikçe sizlere daha fazlasını aktarmaya çalışıyor olacağım. Tekrardan görüşünceye dek hepinize mutlu günler dilerim. 

CodeContracts.rar (25,40 kb)

C# 4.0 - COM Interop İyileştirmelerinden Dynamic Import ve Ommiting Ref [Beta 2]

Salı, 15 Aralık 2009 09:30 by bsenyurt

Merhaba Arkadaşlar,

Hani bazen insanın canı şöyle çıtır çıtır kuruyemiş çeker ya...Hatta çoğunlukla bir film seyrederken, maç izlerken, arkadaşları ile sohhet ederken, internette surf yaparken iyi gider ya...Hatta birisinin blog yazısını okurken kuruyemişleri yerken daha bir heyecanlı, istekli olur ya... Laughing İşte bende bu düşünceyle yola çıkıp siz değerli okurlarım kuruyemiş yerken kısa zamanda bir şeyler öğrenebilin, keyifli bir on dakika geçirin diye bu yazıyı hazırladım. Bakalım bu yazımızda bizleri hangi macera bekliyor.

Bildiğiniz üzere C# 4.0 ile birlikte yine köklü dil değişiklikleri hayatımıza girmiş bulunmakta. Özellikle dinamik diller ile olan etkileşimin arttırılması ve COM dünyası ile olan haberleşmede getirilen yenilikler son derece önemli. Bu gelen yenilikler arasında dynamic anahtar kelimesi, opsiyonal ve isimlendirilmiş parametrelerde(Optional & Named Parameters) en çok göze çarpanlar arasında yer almakta. Ancak çok fazla irdelenmeyen fakat özellikle COM Interop dünyasını ilgilendiren minik ve önemli iyileştirmelerde bulunmakta. Bu neden bu kısa yazımızda söz konusu minik iyileştirmelerden ikisini çok basit olarak incelemeye çalışıyor olacağız.

Kişisel Not : Bu konuda en doğru bilgilere ve güncel örneklere yazının hazırlandığı tarih itibariyle MSDN sitesinden ulaşabileceğinizi de hatırlatmak isterim.

Dynamic Import;

Dynamic anahtar kelimesi yardımıyla static olmayan ve COM, dinamik diller gibi ortamlardan gelen nesnelerin ele alınabilmesi mümkün hale gelmektedir. Peki COM Interop ile olan etkileşimde Dynamic tiplerin çaktırmadan geldiğini biliyor muydunuz? Dilerseniz konuyu irdelemek için aşağıdaki kod parçasını göz önüne alalım.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Excel = Microsoft.Office.Interop.Excel;

namespace COMInteropFeatures
{
    class Program
    {
        static void Main(string[] args)
        {
            var excelApp = new Excel.Application();
            excelApp.Workbooks.Add();
            excelApp.Visible = true;

            #region DynamicImport özelliği olmadan önce

            ((Excel.Range)excelApp.Cells[1, 1]).Value2 = "ID";

            Excel.Range range12 = (Excel.Range)excelApp.Cells[1, 2];
            range12.Value2 = "Phone Number";

            #endregion
        }
    }
}

Bu örnekte Excel API' sine ulaşılaraktan bir Workbook oluşturulması ve aşağıdaki görüntünün üretilmesi sağlanmaktadır.

Üzerinde önemle durmamız gereken nokta ise 1,1 ile 1,2 koordinatlarındaki hücrelere veriyi nasıl yazdığımızdır. Dikkat edilecek olursa 1,1 hücresine yazı yazmak için Range tipine bir dönüştürme işlemi yapılmıştır. Bu dönüşüm işlemi sonrasında Value2 özelliğine ulaşılabilmiştir. Devam eden kod satırında ise önce Range tipine dönüştürme ve atama işlemi yapılmış, sonrasında Value2 özelliğine gidilmiştir. Bu açıdan bakıldığında bir dönüştürme işlemi yapılmasının kaçınılmaz olduğu görülmektedir. Elbette bu eskiden böyleydi. Artık dynamic tipinin COM Interop nesneleri içerisine serpiştirildiğini görmekteyiz. Aşağıdaki görüntü bu durumu son derece iyi açıklamaktadır.

Görüldüğü gibi Cells üzerinden ulaşılan tip dynamic olarak ele alınmaktadır. Buna göre yukarıdaki kod parçası dynamic import kabiliyeti sayesinde dönüştürme işlemlerine gerek duyulmadan aşağıdaki gibi yazılabilir.

excelApp.Cells[1, 1].Value = "ID";

Excel.Range range12_ = excelApp.Cells[1, 2]; // Cast yapılmadan doğrudan atama işlemi
range12_.Value2 = "Phone Number";

Sonuç aynı olacaktır. Dikkat edilmesi gereken nokta herhangibir cast işlemi yapılmasına gerek olmayışıdır. Diğer yandan dynamic olan tiplerin çalışma zamanında çözümlenmesi söz konusu olduğundan aşağıdaki durumda bir handikap olarak görülebilir.

Cells[1,2]. sonrasında kullanabileceğimiz metodları gören, bilen, hatırlayan var mı? Undecided

Omitting Ref;

Yine COM Interop nesneleri ile olan münasibetlerimizde yaşadığımız sorunlardan biriside ref tipinden parametrelerin aktarılması için mutlaka geçici de olsa değişken tanımlamaları yapılması gerekliliğidir. Durumu daha net anlayabilmek için aşağıdaki kod parçasını göz önüne alalım.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Word = Microsoft.Office.Interop.Word;

namespace COMInteropFeatures
{
    class Program
    {
        static void Main(string[] args)
        {
            #region omitting ref

            #region öncesi

            Word.Application wordApp = new Word.Application();
            wordApp.Visible = true;
            object filePath = Environment.CurrentDirectory+"\\Belge.docx";
            object missing = Type.Missing;

            wordApp.Documents.Open(ref filePath, ref missing, ref missing, ref missing, ref missing, ref missing,  ref missing, ref missing, ref missing, ref missing, ref missing,  ref missing, ref missing, ref missing, ref missing, ref missing);

            #endregion

            #endregion
        }
    }
}

Bu seferki örneğimizde çok basit olarak Word Interop nesnesini kullanarak Belge.docx isimli dosyanın açılması sağlanmaktadır. Ancak Open metodunun yazılışı mutlaka dikkatinizi çekmiştir. Peki bir sürü ref missing yazmamış dışında bir sıkıntı görebiliyor musnuz? Wink Aslında Named ve Optional Parametre özellikleri ile bu kod stilinden zaten kurtulduk. Ne varki buradaki sıkınta bu değil. Sıkıntı, ref tipinden olan parametreler için missing isimli object tipinden bir değişken tanımlamak zorunda olmamız. Bu birden fazla çeşitte ref parametresi alan bir COM Interop çağrısı için birden fazla geçici değişken tanımlamak zorunda kalabiliriz anlamına da gelmekte. İşte C# 4.0 ile gelen Omitting Ref(ref' leri göz ardı etmek olarak düşünebiliriz) kabiliyeti sayesinde artık ref olarak kullanılması gereken parametrelere değer türü(Value Type) şeklinde argüman geçirebilmekteyiz. Peki ref kullanımından kaçılıyor mu? Elbetteki hayır. Arka planda derleyici bizim için gerekli geçici değişkenleri zaten oluşturuyor ve metod yine referans tipinden gelen parametreler ile çalışıyor. Kısacası yukarıdaki kodu aşağıdaki şekilde yazmamız mümkün.

wordApp.Documents.Open(filePath, Type.Missing, Type.Missing);

Dikkat edileceği üzere doğrudan değer ataması yapılmış, herhangibir değişken kullanımına gidilmemiştir.

İşte sizlere bir kaç dakika içerisinde çerez niyetine okuyup öğrenebileceğiniz bir yazı. Umarım faydalı olmuştur. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

.Net 4.0 - Lazy Initialization [Beta 2]

Pazartesi, 14 Aralık 2009 10:30 by bsenyurt

Merhaba Arkadaşlar,

Eminim hepimiz arada sırada tembellik yapıyor ve ilk bulduğumuz rahat köşeye kıvrılıp hiç bir şeyi düşünmeden rahatça uyuyabiliyoruz. Eğer bulunduğumuz yer uyumaya çok müsait değilse yandaki kedi gibi ortama ayak uydurup yinede uyuyoruz Smile Siz hiç gözleri açık uyuyabilen insanlar gördünüz mü? Bunun adı düpe düze tembellik olabiliyor bazen. İhtiyaç dışında uyumak ve hiç bir şeyle uğraşmadan öylece kala kalmak tembelliğin doruk noktaya ulaştığı anlar olarak düşünülebilir. Ama gelin görün ki, programatik ortamda da nesnelerin zaman zaman tembellik etmesi gerekmektedir. Bugünkü yazımızda bu konuyu değerlendiriyor olacağız. Peki neymiş şu Lazy Initialization bir bakalım.

Lazy Initialization yetenekleri sayesinde programların gereksiz bellek tüketimlerinin önüne geçilebilir ve ayrıca performans kazanımı sağlanabilir. Aslında uygulamalarımızda Lazy Initialization kullanmamız için gerek ve yeter iki sebep bulunmaktadır. İlk olarak üretilme maliyetleri yüksek olan nesnelerin tanımlandıkları anda oluşturulmaları yerine, kullanılmaya başladıkları yerde oluşturulmaları gerektiği durumlarda tercih edilebilir. Bu noktada aklımıza LINQ içerisinde zaten var olan Deferred Execution özelliği gelmektedir ki Lazy Initialization yeteneklerine sahip .Net CLR tipleride tam olarak bunu sağlamak üzere tasarlanmıştır. İkinci bir sebep olarak, maliyeti yüksek olan bazı hesaplamaların tamamlanmasından sonra ilgili nesnelerin oluşturulması istenebilir. Söz gelimi uygulamamın çalıştırılması ile üretilen n sayıda nesneden sadece gerekli olanların üretilmesi istendiği durumlarda Lazy Initialization yeteneklerinden yararlanılabilir.

Çok doğal olarak nesnelerin ihtiyaç duyuldukları anda oluşturulmasının sağlanması için kendi tekniklerimizi de geliştirebiliriz. Bu noktada Reflection' ın büyük önemi olduğunu vurgulamak gerekir. Nitekim nesnelerin oluşturulmasI işlemini üstlenecek bir Wrapper' ın mutlaka tasarlanmış olması ve hatta içerisinde Instance üretimi için gerekli kodlamaların yapılması gerekmektedir. Activator sınıfının çalışma zamanında nesne üretimi ile ilişkili çeşitli static metodları bu amaçla kullanılabilir. Ancak .Net Framework 4.0 ile birlikte Lazy Initialization için kullanılabilecek Lazy<T> sarmalayıcı sınıfı gelmektedir. Bu tip sayesinden nesnelerin gerek duyulduğu zamanlarda oluşturulmasının sağlanması son derece kolaylaştırılmaktadır. Üstelik Lazy<T> Thread Safe bir tip olarak kullanılabilmektedir.

Dilerseniz konuyu biraz daha net kavramak adına basit bir örnek geliştirelim. Örnek senaryomuza göre program içerisinde ele alınan bir oyun sahnesi ve bu sahne içerisinde yer alan grafiksel şekillerin tutulduğu çeşitli nesnelerin üretim işleminde Lazy Initialization tekniklerinin nasıl kullanıldığını incelemeye çalışacağız. Bu senaryoda Lazy Initialization kullanmamız için öne sürdüğümüz sebep ise şu olacak; programın ilerleyen adımlarında pek çok farklı sahne ve bu sahne içerisinde yer alan grafik nesnelerin yeri geldiğinde oluşturulması ve böylece programın başlangıcında n sayıda sahne için yapılacak olan oluşturulma maliyetinin azaltılması. Bu amaçla ilk olarak Visual Studio 2010 Ultimate Beta 2 üzerinde aşağıdaki tiplere sahip bir Console uygulaması geliştirdiğimizi düşünelim.

using System.Collections.Generic;

namespace BeLazy
{
    enum ActorType
    {
        Player,
        Computer,
        Wall,
        Enemy
    }
    class Actor
    {
        public int ActorId { get; set; }
        public string Title { get; set; }
        public string Capability { get; set; }
        public ActorType ActorType { get; set; }
        public byte[] Image { get; set; }

        public override string ToString()
        {
            return string.Format("Actor Id {0} Title {1} Capability {2} Actor Type {3}", ActorId.ToString(), Title, Capability,ActorType.ToString());
        }
    }
    class Scene
    {
        public int SceneId { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public byte[] Background { get; set; }
        public List<Actor> Actors { get; set; }

        public override string ToString()
        {
            return string.Format("SceneId {0} Widht {1} Height {2}", SceneId.ToString(), Width.ToString(), Height.ToString());
        }
    }
}

Tamamen hayali olarak tasarlanmış olan bu tiplerde ana fikir Scene tipi içerisinde bir Actor listesinin olmasıdır. Her iki tip içerisinde byte[] tipinden özellikler yer almaktadır. Scene nesnelerinin sayısının çok fazla olduğu bir durumda, tümünün bir seferde oluşturulması bellek üzerindeki tüketimi arttırabileceği gibi program performansınıda olumsuz yönde etkileyebilir. Bu nedenle Lazy Initialization tekniklerinden faydalanarak sadece gereksinim duyulduğu yerde oluşturulma işlemi yolu tercih edilebilir.

Peki oluşturulma işlemi hangi aşamada gerçekleşmektedir? Bu noktada Lazy<T> tipinin Value özelliği devreye girer. Value özelliğinin kullanıldığı ilk yerde T tipinin oluşturulma işlemi başlamaktadır. Şimdi bu durumu analiz etmek amacıyla Program kodunun içeriğini aşağıdaki gibi yazdığımızı düşünelim.

using System;

namespace BeLazy
{
    class Program
    {
        static void Main(string[] args)
        {
            Lazy<Scene> lazyScene = new Lazy<Scene>();

            Console.WriteLine("Scene oluşturuldu mu? {0} ",lazyScene.IsValueCreated);
            lazyScene.Value.SceneId = 10;
            Console.WriteLine("Scene oluşturuldu mu? {0} ", lazyScene.IsValueCreated);
            Console.WriteLine("Scene ID : {0}",lazyScene.Value.SceneId.ToString());
        }
    }
}

İlk olarak Lazy<Scene> tipinden bir nesne örneği oluşturulmaktadır. Sonrasında IsValueCreated özelliği yardımıyla Scene nesnesinin oluşturulup oluşturulmadığına bakılır. Bir sonraki satırda dikkat edileceği üzere Value özelliği üzerinden gidilerek SceneId için değer ataması yapılmıştır. Uygulamanın çalışma zamanı çıktısı aşağıdaki gibidir.

Volaaaa!!! Cool Görüldüğü üzere Value özelliği çağırılana kadar IsValueCreated özelliği false değer döndürmektedir. Bir başka deyişle söz konusu nesne henüz oluşturulmamıştır. Ancak Value özelliğinin kullanılması ile birlikte bir Scene nesnesinin örneklendiği görülmektedir. Burada dikkat edilmesi gereken noktalardan biriside Value özelliğinin Readonly olmasıdır. Bir başka deyişle Value özelliği ile elde edilen bir nesne referansına yeni bir atama gerçekleştirilemez.

Ama tabiki Value üzerinden gidilen tipin özellikleri değiştirilebilir. Örneğimizde dikkat çekici noktalardan bir diğeri de Scene nesnesinin aslında karmaşık bir tip olarak içerisinde başka tipleride barındırıyor olmasıdır. Buna göre Value özelliğinden yararlanılarak diğer tiplerin değerlerinin de atanması sağlanabilir...Ya da...Ya da aşağıdaki kod parçasında olduğu gibi tembel bir üretim gerçekleştirilebilir.

using System;
using System.Collections.Generic;

namespace BeLazy
{
    class Program
    {
        static void Main(string[] args)
        {
            Lazy<Actor> azman = new Lazy<Actor>(() =>
                new Actor {
                     ActorId=1,
                      ActorType= ActorType.Computer,
                       Capability="Çoook!",
                        Image=new byte[1000],
                         Title="Azman"
                }
                );

            Lazy<Actor> gazman = new Lazy<Actor>(() =>
                new Actor
                {
                    ActorId = 1,
                    ActorType = ActorType.Enemy,
                    Capability = "Yüksek Gaz depolama kapasitesi!",
                    Image = new byte[5000],
                    Title = "Gazman"
                }
                );

            Lazy<Scene> scene = new Lazy<Scene>(() =>
                new Scene
                {
                    SceneId = 1001,
                    Background = new byte[10000],
                    Height = 100,
                    Width = 250,
                    Actors = new List<Actor> { azman.Value, gazman.Value }
                }
                );

            Console.WriteLine("Scene oluşturuldu mu? {0} ",scene.IsValueCreated);
            Console.WriteLine("Azman oluşturuldu mu= {0}",azman.IsValueCreated);
            Console.WriteLine("Gazman oluşturuldu mu= {0}", gazman.IsValueCreated);
           
            Scene currentScene=scene.Value;

            Console.WriteLine("Scene oluşturuldu mu? {0} ", scene.IsValueCreated);
            Console.WriteLine("Azman oluşturuldu mu= {0}", azman.IsValueCreated);
            Console.WriteLine("Gazman oluşturuldu mu= {0}", gazman.IsValueCreated);     

            Console.WriteLine(currentScene.ToString());

            foreach (Actor actor in currentScene.Actors)
            {
                Console.WriteLine("\t {0}",actor.ToString());
            }
        }
    }
}

Buna göre çalışma zamanı içeriği aşağıdaki gibi olacaktır.

Görüldüğü üzere Scene ve Actors özelliğinin işaret ettiği koleksiyon içerisindeki tüm Actor tipleri Lazy olarak üretilmektedir. Üretim noktası ise currentScene değişkenin Value özelliği ile atamanın yapıldığı satırdır. Bu satırdan sonra Scene nesnesi ve içeriğindeki Actor nesnelerinin oluşturulması gerçekleştirilmektedir. Tabi istenirse Actor oluşturma işlemleride sonraki bir adıma bırakılabilir.

İlginç olan bir noktada, Lazy<T> tipinden özelliklerin(Type Property) tanımlanabilmesidir. Dilerseniz aşağıdaki örnek kod parçasını göz önüne alalım.

using System;
using System.Collections.Generic;

namespace BeLazy
{
    class Program
    {
        static void Main(string[] args)
        {
            Contact cntc = new Contact(1, "Burak Selim Şenyurt", "New York", "USA", "1000");
            Console.WriteLine("Contact içerisindeki Address nesnesi üretilmiş mi ? {0}", cntc.IsAddressCreated);
            Console.WriteLine("{0} {1} {2}",cntc.Address.Country,cntc.Address.City,cntc.Address.PostalCode);
            Console.WriteLine("Contact içerisindeki Address nesnesi üretilmiş mi ? {0}", cntc.IsAddressCreated);
        }
    }

    class Contact
    {
        private Lazy<Address> _address;

        public Contact(int contactId,string name,string city,string country,string postalCode)
        {
            _address = new Lazy<Address>(
                () => new Address {
                     City=city,
                     Country=country,
                     PostalCode=postalCode
                }
                );

            ContactId = contactId;
            Name = name;
        }

        public Address Address
        {
            get
            {
                return _address.Value;
            }
        }
        public bool IsAddressCreated
        {
            get
            {
                return _address.IsValueCreated;
            }
        }
        public int ContactId { get; set; }
        public string Name { get; set; }
       
    }

    class Address
    {
        public string City { get; set; }
        public string Country { get; set; }
        public string PostalCode { get; set; }
    }
}

Örnekte yer alan Contact sınıfı içerisinde Lazy<Address> tipinden private bir alan tanımlandığı görülmektedir. Bu alanın oluşturulması Contact sınıfının yapıcı metodu içerisinde gerçekleştirilmektedir. Address isimli özellik ilede, Lazy<Address> tipinden olan _address alanının Value özelliğinin değeri geriye döndürülmektedir. Zaten Value özelliğine referans atanması gerçekleştirilemediğinden Address isimli özellik sadece get bloğuna sahip olacak şekilde tanımlanmıştır(Read Only). Bu kodlamaya göre Contact sınıfına ait bir nesne örneklendikten sonra içerisinde yer alan Address nesnesi hemen oluşturulmaz. Ancak Contact sınıfının nesne örneği üzerinden Address özelliğine gidilir ve Value özelliğine ulaşılırsa örnekleme işlemi gerçekleşecektir. Bu durum çalışma zamanında daha net bir şekilde görülebilir.

Dikkat edileceği üzere Contact nesnesi örneklense bile Address nesnesi henüz oluşturulmamıştır. Ancak Address nesnesinin Value özelliğine ulaşıldığı ilk yerde bu işlem gerçekleşmektedir.

Lazy<T> tipinin kullanımında dikkatli olunması gereken noktalardan biriside Multi-Thread operasyonlardır. Özellikle birden fazla Thread' in aynı Lazy<T> nesne örneğini kullanması halinde ilgili nesnenin hangi Thread içerisinde oluşturulacağının bir önemi kalmamaktadır. Ancak oluşturulma işlemleri sırasında meydana gelecek istisnalarda(Exceptions) diğer Thread' lerinde otomatik olarak etkilenmesi söz konusudur. Buna ek olarak Lazy<T> tipleri oluşturulurken Thread Safe olup olmayacakları çok basit bir şekilde belirlenebilir. Bu durmu daha net bir şekilde anlayabilmek adına dilerseniz aşağıdaki kod parçasını göz önüne alalım.

using System;
using System.Collections.Generic;
using System.Threading;

namespace BeLazy
{
    class Program
    {
        static void Main(string[] args)
        {
            Lazy<Information> information = new Lazy<Information>();

            Thread threadA=new Thread(
                ()=>
                    {
                        Console.WriteLine(information.Value.ToString());
                        Console.WriteLine("\tThread A Thread Id : {0}",Thread.CurrentThread.ManagedThreadId.ToString());
                    }
                    );

            Thread threadB = new Thread(
                () =>
                {
                    Console.WriteLine(information.Value.ToString());
                    Console.WriteLine("\tThread B Thread Id : {0}", Thread.CurrentThread.ManagedThreadId.ToString());
                }
                    );

            Thread threadC = new Thread(
                () =>
                {
                    Console.WriteLine(information.Value.ToString());
                    Console.WriteLine("\tThread C Thread Id : {0}", Thread.CurrentThread.ManagedThreadId.ToString());
                }
                    );

            threadA.Start();
            threadB.Start();
            threadC.Start();

            threadA.Join();
            threadB.Join();
            threadC.Join();
        }
    }

    class Information
    {
        private int threadId;
        private string initializeTime;

        public Information()
        {
            threadId = Thread.CurrentThread.ManagedThreadId;
            initializeTime = DateTime.Now.ToLongTimeString();
            Thread.Sleep(2000);
        }

        public override string ToString()
        {
            return String.Format("Thread Id : {0} Time : {1}", threadId.ToString(), initializeTime);
        }
    }
}

Örneğimizde 3 farklı Thread' in çalıştırıldığı görülmektedir. Bu Thread' lerin tamamı kendi içlerinde Lazy<Information> tipinden olan information değişkenini kullanmaktadır. Buna göre Thread' lerden hangisi ilk olarak Value özelliğine erişirse, Information nesnesi örneklemiş olacaktır. Buna ek olarak çalışma zamanındaki sonuçlara bakıldığında tüm Thread' lerin ilk üretilen Information nesne örneğine ulaştığı bu nedenle Information yapıcı metodunda oluşturulan ManagedThreadId değerlerinin ve zamanların aynı olduğu görülür. İşte sonuçlar.

Bu senaryoya göre nesnenin hangi Thread içerisinde oluşturulduğunun hiç bir önemi yoktur. Şimdi akla şöyle bir soru gelebilir. Lazy<Information> örneği oluşturulurken Thread-Safe davranılacağı nerede belirtilmiştir?

Aslında Lazy<T> tipinin isThreadSafe parametresinin değeri varsayılan olarak true' dur ve istenirse yapıcı metod içerisinde değiştirilebilir. Özelliğe false değer atanması halinde genellikle ilgili nesnenin tek bir thread içerisinde kullanıldığı varsayılır ve bu azda olsa performans kazanımına neden olur. Ancak birden fazla thread' in aynı Lazy<T> nesnesini kullanacağı hallerde koordinasyon daha büyük önem kazanmaktadır ve bu nedenle isThreadSafe özelliğinin değerinin true olarak bırakılması önerilir.

Kişisel Not : Özellikle birden fazla Thread' in aynı nesnenin kendi içlerinde farklı kopyalarını alarak kullanmaları istendiği durumda .Net 4.0 ile gelen ThreadLocal tipinden yararlanılabilir ki .Net 3.5' te bunun için ThreadStatic isimli bir nitelikten yararlanılmaktadır. İşte size güzel bir araştırma konusu Laughing

Buraya kadar anlattıklarımıza göre dikkat edilmesi gereken bazı noktalar olduğuda aşikardır;

  • Lazy<T> içerisinde kullanılacak tipin mutlaka varsayılan yapıcı(Default Constructor) metodu olmalıdır. Nitekim oluşturma işleminde aslında Activator sınıfının CreateInstance metodu devreye girmektedir.
  • Value özelliği ReadOnly' dir. Bu nedenle kullanıldıktan sonra T tipinden bir referansın atanması gerçekleştirilemez.
  • Multi-Thread senaryolarda isThreadSafe özelliğini true olarak bırakmak doğru bir yaklaşımdır.
  • Single-Thread senaryolarda isThreadSafe özelliği performans kazanımı adına istenirse false bırakılabilir.
  • Value özelliğine erişildiğinde oluşabilecek bir istisna halinde kodun ilerleyen kısımlarında Value özelliğine giden her çağrı için aynı Exception örneği söz konusu olacaktır. Yani Value özelliğinin ilk çağırıldığı yerde oluşacak bir istisna devam eden Value çağrılarına da akacaktır. Bu Multi-Thread senaryolarda da isThreadSafe özelliğinin değeri true bırakıldığı sürece de geçerlidir.
  • Çekinilmesi gereken nokta şudur; isThreadSafe özelliğine false değer verilmesi halinde thread' ler arasında senkronizasyon ortadan kalkacağından, bir Thread' in exception gördüğü noktada diğer bir thread kullanılabilir bir Value özelliği ile karşılaşabilir. Aman dikkat.Sealed

Umarım faydalı bir yazı olmuştur. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

BeLazy.rar (28,31 kb)

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