All posts by Soner Gönül

Workflow Foundation ile Flowchart Aktivitesi Oluşturmak

Bu yazımızda Flowchart aktivitesi içeren workflow’ların nasıl oluşturulduğunu göreceğiz. Adından da anlaşılacağı gibi bir Flowchart aktivitesi aynı bir flowchart gibi çalışır. Aktiviteler birbirlerine karar yapıları ile bağlıdırlar (decision trees). Daha önceki yazılarımızda gördüğümüz gibi Sequence aktivitesi içerisinde aktiviteler sıralı olarak yukarıdan aşağıya çalışırlar. Fakat bir Flowchart aktivitesi içerisinde aktiviteler karar dallanmasına bağlı olarak herhangi bir sırada çalışabilirler.

Hemen bununla ilgili bir proje oluşturalım. Visual Studio 2010 –> File –> New –> Project –> Visual C# –> Workflow –> Workflow Console Application adımlarını takip edelim.

Oluşturduğumuz aktiviteye bir Flowchart aktivitesi sürükleyip bırakalım.

WF-Flowchart-Activity

Ortadaki yeşil daire flowchart aktivitemizin başlangıç düğümünü gösterir. Aşağıdaki boşluk alan da flowchart’ımızı oluşturmak için ekleyeceğimiz aktiviteleri içerme görevini üstlenir.

Bir Flowchart aktivitesi ile Sequence aktivitesi arasındaki temel fark alt aktivitelerin birbirlerine nasıl bağlandıklarıdır. Önceki yazılarımıza baktığımızda Sequence aktivitelerinin içindeki aktiviteler daima yukarıdan aşağıya doğru çalışırlar. Aktivitelerin çalışma sıralarını yerlerini değiştirerek kontrol edebiliriz. Flowchart aktivitesinde ise aktiviteleri diagram içerisinde istediğiniz yere koyabilirsiniz. Daha da önemlisi, bu aktivitelerin aralarına oklar çizmek zorundayız. Tabi Flowchart aktivitesi içerisinde, bir önceki aktiviteye de bağlantı kurabilirsiniz.

Hemen Flowchart aktivitemizin içerisine yeşil dairenin alt tarafına bir adet Writeline aktivitesi ekleyelim. DisplayName özelliği “Merhaba”, Text özelliği de “Merhaba Dünya” olsun.

Başlangıç düğümümüzün üzerine fare ile geldiğimizde dört kenarında gri kutucuklar belirecektir. Bu kutucuklardan birini tıklayıp Merhaba adındaki Writeline aktivitemizin kenarlarına bağlantı görününceye kadar sürükleyip bırakalım.

WF-Start-Node

Bu bize iki aktivitenin nasıl birbirlerine bağlandığı konusunda fikir verir.

Şimdi de diagramımızdaki Writeline aktivitemizin altına bir FlowDecision aktivitesi sürükleyip bırakalım. Bu aktivite sarı bir elmas şeklindedir tıpkı normal bir flowchart diagramındaki karar yapıları gibi. Bu aktivitenin üzerine tıklayıp Properties penceresinden Condition kısmına DateTime.Now.Hour > 12 ifadesini yerleştirelim. Bu ifadeyi yazdıktan sonra FlowDecision aktivitesinin üzerine gelip bu koşul ifadesini görebiliriz.

WF-FlowDecision

Ayrıca yukarıdaki sarı elmas şeklinin sağ üst köşesindeki sarı ok ile koşul ifadesini diagrama sabitleyebiliriz. Burada gördüğümüz gibi aktivitenin sol tarafında True sağ tarafında ise False dalları bulunur. Properties penceresinden TrueLabel özelliğine “Öğleden Sonra”, FalseLabel özelliğine de “Sabah” yazalım. Bu aktivitenin üzerine geldiğimizde bu özelliklerin isimlerini sağ ve sol tarafta görebiliriz.

Bu aşamalardan sonra Merhaba Writeline aktivitemizi FlowDecision aktivitemize bağlayalım. Sonra da FlowDecision aktivitesinin sağ tarafına yeni bir Writeline aktivitesi sürükleyip bırakalım. DisplayName özelliğini “Sabah”, Text özelliğini de “İyi Sabahlar” olarak ayarlayalım. Ardından FlowDecision aktivitemizin Sabah bağlantısını yeni eklediğimiz Sabah adındaki Writeline aktivitemize bağlayalım.

WF-Connection

NOT: Göreceğiniz gibi FlowDecision aktivitesinde DisplayName özelliği bulunmuyor.

Başka bir FlowDecision aktivitesini ilk eklediğimiz olanın soluna sürükleyip bırakalım. Bu aktivitenin Condition kısmına DateTime.Now.Hour >= 18 ifadesini yazalım. İlk FlowDecision aktivitemizin “Öğleden Sonra” dalını bu yeni eklediğimiz aktiviteye bağlayalım. Yeni aktivitemizin FalseLabel özelliğini “Öğleden Sonra”, TrueLabel özelliğini de “Akşam” olarak ayarlayalım. Ardından 2 adet Writeline aktivitesi ekleyelim. Sırayısla DisplayName özelliklerine “Akşam”, “Öğleden Sonra”, Text özelliklerine de “İyi Akşamlar” ve “İyi Öğle Sonraları” yazalım. Daha sonra ikinci eklediğimiz FlowDecision aktivitesini bu Writeline aktivitelerine bağlayalım.

WF-FlowDecision2

Herşey tamam. Şimdi projemizi çalıştırmadan önce Program.cs dosyamızı açalım. WorkflowInvoker sınıfı içerisinden çağırdığımız Invoke() methodu ardından aşağıdaki kodları yazalım;


Console.WriteLine("ENTER to exit");
Console.ReadLine();

Projemizi F5 ile çalıştıralım. Günün hangi saatinde olduğunuza göre aşağıdakine benzer bir sonuç alacaksınız;

Merhaba Dünya
İyi Akşamlar
ENTER to exit

Bu yazımızda FlowDecision aktivitesinin nasıl kullanıldığına dair bilgiler vermeye çalıştım.

Workflow Foundation 4.0 ile Workflow Kodlamak

Daha önceki yazılarda workflow’ları designer kısmından sürükle bırak yöntemi ile diagramımıza ekliyorduk. Bu workflow’ları kod yardımı ile de diagramımıza uygulayabiliriz. Her workflow kod ile ya da tasarım ile diagramlara eklenebilir, bu tamamen seçim meselesi. Fakat kod ile workflow’ları uygulamak size bu yapıların nasıl çalıştığına dair daha fazla bilgiler verecektir.

Başlangıç için bir Visual C# konsol uygulaması oluşturalım (WF konsol uygulaması değil!!). Daha sonra Solution Explorer kısmından Reference’a sağ tıklayıp Add Reference seçeneğini seçelim. Buradan System.Activities referansını seçip projemize ekleyelim. Bu durum bize workflow aktivitelerini kodumuzun içerisinde kullanma imkanı verir. Daha sonra kod kısmından aşağıdaki namespace’leri de ekleyelim;


using System.Activities.Statements;
using System.Activities.Expressions;

Daha sonra Main() methodumuzun içerisine aşağıdaki kodumuzu yazalım;


WorkflowInvoker.Invoke(WorkflowOlustur());
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();

Burada WorkflowOlustur() methodumuzu biz kendimiz yazacağız. Bir önceki yazımda bahsettiğim gibi bir workflow sadece iç içe geçmiş özellikler topluluğudur. Daha doğrusu, iç içe geçmiş sınıflar ve bunları özellikleri topluluğudur. Şimdi WorkflowOlustur() methodumuzu yazmaya başlayalım.


static Activity WorkflowOlustur()
{
Variable<int> NumberOfBells = new Variable<int>()
{
Name = "NumberOfBells",
Default= DateTime.Now.Hour
};

Variable<int> Counter = new Variable<int>()
{
Name = "Counter",
Default = 1
};
return new Sequence()
{

};
}

WorkflowOlustur() methodumuz öncelikle 2 adet int tipinde Variable<T> template sınıfı oluşturduk. (NumberOfBells ve Counter) Bunlar çeşitli aktiviteler tarafından kullanılacak olan değişkenlerdir.

WorkflowInvoker sınıfının beklediği gibi, WorkflowOlustur() methodumuz geriye Activity döndürecek şekilde tanımlandı. Aslında geriye anonim bir Sequence sınıf örneği döndürür. Activity sınıfı, tüm workflow aktivitelerinin türetildiği ana sınıftır, buna Sequence dahil. Şimdi var olan kodumuzu adım adı geliştirmeye başlayalım;

1. Adım: Şimdiye kadar kodumuzda boş bir Sequence aktivitesi oluşturmuştuk. Bu kabaca Sequence’i olup hiçbir aktivitesi olmayan yeni bir workflow yaratmaya benzer. Şimdi yukarıda tanımladığımız Sequence() sınıfımızı yazalım;


return new Sequence()
{
DisplayName = "Main Sequence",
Variables = { NumberOfBells, Counter },
Activities =
{
new WriteLine()
{
DisplayName = "Merhaba",
Text = "Merhaba Dünya"
},
new If()
{
DisplayName = "PM için ayarla"
},
new While()
{
DisplayName = "Zil Sesi"
},
new WriteLine()
{
DisplayName = "Zamanı Göster",
Text = "Şu anki zaman" + DateTime.Now.ToString()
},
new If()
{
DisplayName = "Selam"
},
}
};

Yukarıdaki kodumuz önce aktiviteler için DisplayName ve içerisinde kullanılacak Variable nesnelerini tanımlar. Daha sonra da aşağıdaki aktiviteleri belirtilen DisplayName özelliği ile tanımlar.

Type DisplayName
Writeline Merhaba
If PM için ayarla
While Zil Sesi
Writeline Zamanı Göster
If Selam

2. Adım: Şimdi ilk If aktivitemizin içerisine koşul ifadelerini yazalım.


new If()
{
DisplayName = "PM için ayarla",
Condition = ExpressionServices.Convert<bool>
(env => NumberOfBells.Get(env) > 12),
Then = new Assign<int>()
{
DisplayName = "Zili Ayarla"
}
},

Bu kod if aktivitesinin koşul (condition) ve then kısımlarını tanımlar. Şimdi biraz Condition kısmına biraz daha yakından bakalım.

Deyimler

ExpressionServices sınıfının statik Convert<T>() methodu bir InArgument<T> sınıfı oluşturmak için kullanılır ki bu da tam olarak Condition özelliğinin istediği şeydir. Bu sınıf ve methodlar generic tipinde (<T>) olduklarından, her veri tipi için kullanılabilirler. Bu durumda bool tipini kullanmamız gerekir çünkü if aktivitesinin Condition özelliği sadece true ya da false destekler.

Bu deyim lambda expression kullanarak veriyi workflow’un çevresinden dışarı çıkartır. Burada gösterdiğimiz lambda expression deyiminde => lambda operatörü olarak gösterilir. Bu operatörün solundakiler giriş parametrelerini ifade ettiği gibi, gerçek koşul ifadesi bu operatörün sağında tanımlanır. env değeri runtime zamanında Condition kısmının çalıştırılmasını sağlar.

Aslında workflow herhangi bir veri elemanı depolamaz. Variable sınıfı basitçe veri tanımlar. Get() methodu ile Variable sınıfından veriler alınır. Get(env) ile geri dönen değeri 12’den büyük olup olmadığını karşılaştırır. Şimdi aşağıdaki kodumuzu While aktivitemize ekleyelim;


new While()
{
DisplayName = "Zil Sesi" ,
Condition = ExpressionServices.Convert<bool>
(env => Counter.Get(env) <= NumberOfBells.Get(env)),
Body = new Sequence()
{
DisplayName = "Zil Sesi"
}
},

Burada kullandığımız Condition kısmı If aktivitesi içerisinde kullandığımız ile özdeş bir kullanım. Tabi ki yine ExpressionServices sınıfınının (bool tipi ile) InArgument<T> sınıfı oluşturmak için kullanıyoruz. Bu durumda Counter <= NumberOfBells koşulunu hesaplarız. Bu iki değer için de, gerçek değerlerini hesaplamak için Get(env) methodunu kullanırız.

Şimdi de Selam adındaki ikinci If ifademize kodumuzu yazmaya başlayalım;


new If()
{
DisplayName = "Selam",
Condition = ExpressionServices.Convert<bool>
(env => DateTime.Now.Hour >= 18),
Then = new WriteLine() { Text = "İyi Akşamlar"},
Else = new WriteLine() { Text = "İyi Günler"}
},

Condition kısmında env giriş parametresi kullanılmaz fakat yine de deyim içerisinde tanımlanmak zorundadır. Mantıklı olarak saatin 6.00 PM’i geçip geçmediğine göre Then ve Else kısımlarında Writeline aktivitelerinde “İyi Akşamlar” ve “İyi Günler” yazdırılır.

Şimdi ilk If aktivitemiz “PM için ayarla” içerisine Assign aktivitesi (Then kısmı olan) oluşturmuştuk. Bu Assign aktivitemizin içerisine aşağıdaki kodlarımızı ekleyelim;


Then = new Assign<int>()
{
DisplayName = "Zili Ayarla",
To = new OutArgument<int>(NumberOfBells),
Value = new InArgument<int>(env => NumberOfBells.Get(env) - 12)
}

Assign Aktivitesi

Assign sınıfı generic tipindedir, yani herhangi bir veri tipini destekler. Bu durumda, integer tipinde bir değeri atamak için Assign<int> şeklinde kullanmamız gerekir. To ve Value özellikleri template sınıf olarak kullanılır ve aynı tipte (<int>) olmak zorundadırlar. To özelliği bir OutArgument sınıfıdır ve yapıcısının içerisinde bir Variable sınıfı olur. Value özelliği bir InArgument sınıfıdır. Yapıcısı için, Condition kısmında yaptığımız gibi lambda expression kullanımına ihtiyaç duyarız.

Sequence

While aktivitesi içerisinde boş bir Sequence aktivitesi oluşturmuştuk. Bunun anlamı while döngüsü her tekrarlandığında belirli olan aktivite çalıştırılır. Şimdi bu Sequence aktivitesine kodlarımızı yazalım;


Body = new Sequence()
{
DisplayName = "Zil Sesi",
Activities =
{
new WriteLine()
{
Text = new InArgument<string>(env => Counter.Get(env).ToString())
},
new Assign<int>()
{
DisplayName = "Sayacı Arttır",
To = new OutArgument<int>(Counter),
Value = new InArgument<int>(env => Counter.Get(env) + 1)
},
new Delay()
{
Duration = TimeSpan.FromSeconds(1)
}
}
}

Bu kod Sequence içerisine 3 adet aktivite ekler;

  • Counter değişkenini göstermek için bir Writeline aktivitesi
  • Counter değişkenini arttırmak için bir Assign aktivitesi
  • Her öteleme sonrasında küçük bir duraklama için Delay aktivitesi

Burada yazdığımız Writeline aktivitesine bakarsak Text özelliği diğer kullanımlar gibi klasik string değil. Burada değer bir deyim ile gösterilir. Get(env) methodu ile geriye int bir değer döndürür bu deyim. Daha sonra da bu deyimi ToString() methodu ile string haline (Text özelliğinin istediği şekilde) getiririz.

Delay aktivitesinde ise Duration özelliğine TimeSpan sınıfının FromSeconds() statik methodu geçirilerek bu aktiviteyi kullanırız.

Şimdi projemizi F5 ile çalıştıralım. Günün hangi saatinde olduğunuza göre değişebilen çıktımız aşağıdaki gibi olacaktır;

Merhaba Dünya
1
2
3
4
5
6
Şu anki zaman 03.08.2012 18:57:11
İyi Akşamlar
Press ENTER to exit

Program.cs dosyamızın tüm kodu aşağıdaki şekildedir;


using System;
using System.Activities;
using System.Activities.Statements;
using System.Activities.Expressions;

namespace WF_Konsol_Uygulama
{
class Program
{
static void Main(string[] args)
{
WorkflowInvoker.Invoke(WorkflowOlustur());
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
}

static Activity WorkflowOlustur()
{
Variable<int> NumberOfBells = new Variable<int>()
{
Name = "NumberOfBells",
Default= DateTime.Now.Hour
};

Variable<int> Counter = new Variable<int>()
{
Name = "Counter",
Default = 1
};
return new Sequence()
{
DisplayName = "Main Sequence",
Variables = { NumberOfBells, Counter },
Activities =
{
new WriteLine()
{
DisplayName = "Merhaba",
Text = "Merhaba Dünya"
},
new If()
{
DisplayName = "PM için ayarla",
Condition = ExpressionServices.Convert<bool>
(env => NumberOfBells.Get(env) > 12),
Then = new Assign<int>()
{
DisplayName = "Zili Ayarla",
To = new OutArgument<int>(NumberOfBells),
Value = new InArgument<int>(env => NumberOfBells.Get(env) - 12)
}

},
new While()
{
DisplayName = "Zil Sesi" ,
Condition = ExpressionServices.Convert<bool>
(env => Counter.Get(env) <= NumberOfBells.Get(env)),
Body = new Sequence()
{
DisplayName = "Zil Sesi",
Activities =
{
new WriteLine()
{
Text = new InArgument<string>(env => Counter.Get(env).ToString())
},
new Assign<int>()
{
DisplayName = "Sayacı Arttır",
To = new OutArgument<int>(Counter),
Value = new InArgument<int>(env => Counter.Get(env) + 1)
},
new Delay()
{
Duration = TimeSpan.FromSeconds(1)
}
}
}
},
new WriteLine()
{
DisplayName = "Zamanı Göster",
Text = "Şu anki zaman " + DateTime.Now.ToString()
},
new If()
{
DisplayName = "Selam",
Condition = ExpressionServices.Convert<bool>
(env => DateTime.Now.Hour >= 18),
Then = new WriteLine() { Text = "İyi Akşamlar"},
Else = new WriteLine() { Text = "İyi Günler"}
},
}

};
}
}
}

Bu yazımızda Workflow Foundation yazı serimizin ilk makalelerinde tasarım kısmı ile aktivite oluşturulmasına karşın kod ile nasıl aktivite oluşturulur ve bu aktivitelerin özelliklerinin hangi tür değişkenler ve ifadeler aldıklarını gördük.

Workflow Foundation 4.0 ile Temel Elemanları Eklemek

Workflow Foundation 4.0, bize kullanmak için If, While, Assign, Sequence gibi yönetimsel elemanlar sunar. Bu yazıda bu elemanların nasıl kullanılacağına dair bilgiler vermeye çalışacağım.

WF 4.0 kullanıcıları workflow elemanları tarafından kullanılan tüm değişkenleri tanımlamak zorundadırlar. Şimdi projemize iki adet değişken tanımlayalım. Birinci değişkenimiz kaç adet zile ihtiyacımız olduğunu gösteren NumberOfBells, diğerini de şimdiye kadar kaç defa zil duyulduğunu gösteren Counter. Peki bu değişkenleri nasıl tanımlıyoruz?

Öncelikle projemize bir adet Sequence aktivitesi ekleyelim. Daha sonra bu aktivitenin üzerine bir defa tıklayıp tasarım kısmında sol altta bulunan “Variables” sekmesine tıklayalım. Daha sonra “Create Variable” kısmına tıklayıp ismi Counter olan tipi de Int32 olan bir değişken kaydedelim. Scope kısmını Sequence olarak bırakabilirsiniz. Bunun anlamı; o değişken Sequence ve onun soyundan gelen tüm aktiviteler tarafından kullanılabilir olmasıdır. Aynı şekilde tekrar “Create Variable” diyelim. Bu sefer değişkenimizi Properties penceresinden tanımlayalım. İsmi NumberOfBells ve tipi de Int32 olan bir değişken tanımlayalım. Scope kısmını yeniden Sequence olarak bırakalım.

WF-VariableDefinition

WF-Properties2

Daha sonra ifadeler yazabileceğimiz yukarıdaki resimde kırmızı kutu içerisinde gösterdiğim Expression Editor’ü açalım. Ve oraya DateAndTime.Now.Hour ifadesini yazalım.

WF-ExpressionEditor

Bu ifadeyi default propertie olarak kaydedelim. Bu işlem NumberOfBells değişkenini günün o andaki saat değerine atar. Bu işlemden sonra değişkenlerimiz aşağıdaki şekilde görünmelidir.

WF-Variables3

Şimdide IF kullanımına bakalım;

DateAndTime sınıfının Hour üyesi geriye 24 saatlik sistemde saati geri döndürür. Örneğin; saat öğleden sonra 3 ise geriye 15 değerini döndürür. Öncelikle diagramımızda bulunan Sequence yapısının içine Toolbox’ta bulunan Writeline aktivitesini sürükleyip bırakalım. Daha sonra bu aktivitenin DisplayName özelliğini Merhaba olarak değiştirelim. Tekrar Sequence içerisine bir If aktivitesi sürükleyip bırakalım.

NOT: Bu adımda If ve Sequence aktivitelerinin sağ üst köşelerinde kırmızı renkli hata kutuları çıkacaktır. If aktivitesindeki kutucuk If aktivitesi içerisinde henüz herhangi bir koşul (condition) yazılmadığını, Sequence aktivitesindeki kutucuk ise ya bu aktivitede ya da alt aktivitelerinde bir hata olduğunu gösterir. Bu hataları kutucukların üstüne gelerek görebilirsiniz.

WF-If

Şimdi If aktivitesini Properties kısmından DisplayName özelliğini “PM için ayarla” olarak değiştirelim. If aktivitesinin 3 kısmı vardır. Condition kısmı hangi koşulun hesaplanacağını gösterir. Bir Boolean (true ya da false) değer çözmelidir bu koşul. Eğer ifade True is Then aktivitesi, eğer ifade False ise Else aktivitesi çalıştırılır. Hem Then hem de Else aktivitelerinin ikisini birden tanımlamak zorunda değiliz. Eğer hiçbir aktivite tanımlanmasa, hiçbir aktivite çalıştırılmaz. Şimdi koşulumuza NumberOfBells>12 ifadesini girelim.

Şimdi de Assign aktivitesini Then kısmımızın içine sürükleyip bırakalım. Assign aktivitesi size bir değişkene ya da bir argümana bir değer atamanıza imkan verir. Hem To hem de Value özelliklerinin bir ifade içerebileceğine dikkat edin. To özelliğine NumberOfBells, Value özelliğine de NumberOfBells – 12 ifadelerini girelim.

WF-Properties2

WF 4.0’da bir çok aktivite compound aktivitedir yani başka aktiviteleri içerebilirler. If aktivitesi buna güzel bir örnek olarak gösterilebilir.

Sıra geldi While aktivitesini kullanmaya. While aktivitesini, ismi “PM için ayarla” olan If aktivitesinin altına sürükleyip bırakalım. Ardından DisplayName özelliğini “Zil Çalınma Sayısı” olarak değiştirelim.

WF-While

While aktivitesi, içerisinde yazılan ifade true olduğu sürece Body kısmındaki aktiviteleri çalıştırır. İlk önce koşul hesaplanır, eğer true ise, içindeki aktiviteler çalıştırılır. Bu durum, koşul false olana kadar devam eder.

NOT: İleride göreceğimiz DoWhile aktivitesi ile While aktivitesi arasındaki fark, programlama olduğu gibidir. DoWhile aktivitesinde önce içindeki aktiviteler bir kez çalıştırılır, sonra koşul çalıştırılıp true olup olmadığı kontrol edilir. While aktivitesinde ise, önce koşul çalıştırılır. Koşul true ise içindeki aktiviteler çalıştırılır. Eğer koşul false ise içindeki ifadeler hiçbir zaman çalıştırılmazlar.

While aktivitesinin koşul kısmına Counter <= NumberOfBells ifadesini yazalım. Daha sonra Body kısmına bir Sequence aktivitesini sürükleyip bırakalım. Bunun da DisplayName özelliğini “Zil Çalınma Sayısı” olarak değiştirelim.

WF-Sequence

Şimdi bu “Zil Çalınma Sayısı” isimli Sequence aktivitemizin içine bir Writeline aktivitesi sürükleyip bırakalım. Writeline aktivitesinin Text özelliğine de Counter.ToString() ifadesini yazalım. Bunun amacı kaç kere zil çaldığını konsola yazdırmaktır. Ardından bu Writeline aktivitesinin altına bir Assign aktivitesi yerleştirelim. To kısmına Counter, Value kısmına ise Counter + 1 yazalım. Bu kısaca Counter değişkenini bir arttıracaktır.

WF-Assign2

Ardından bu Assign aktivitesinin altına bir Delay aktivitesi sürükleyip bırakalım. Kısaca Delay aktivitesi, belirli bir zaman periyodu için o aktiviteyi durdurur. Delay aktivitesinin Duration adında tek bir özelliği vardır. Bu özellik aktivitenin ne kadar duracağını gösterir.  Bu özelliğe TimeSpan.FromSeconds(1) ifadesini yerleştirelim.

“Zil Çalınma Sayısı” adındaki While aktivitesini sağ üst köşesinden daraltalım.

WF-Collapse

Sonra da bu While aktivitesinin altına bir Writeline aktivitesi ekleyelim. DisplayName özelliğini “Zaman:” , Text özelliğini de “Şu anki zaman: ” + DateTime.Now.ToString() şeklinde yazalım.

“Zaman” aktivitesinin altına da bir If aktivitesini sürükleyelim ve ismini de “Selamlama” olarak koyalım. Koşul kısmına da DateTime.Now.Hour >= 18 ifadesini yerleştirelim. Şimdi de Selamlama aktivitesinin içindeki Then ve Else kısımlarına da birer Writeline aktivitesi yerleştirelim. Then kısmındaki aktivitenin Text özelliğine “İyi Akşamlar”, Else kısmındaki aktivitenin Text özelliğine de “İyi Günler” yazalım. Şimdi de projemizi kaydedip F5 ile çalıştıralım.

0
1
2
3
4
5
Şu anki zaman: 30.07.2012 17:00:16
İyi Günler
Press any key to continue . . .

 

Görüleceği üzere 0-5 arası her rakamı yazdırırken 1 saniye bekleme yaptı program. Bu durum Delay aktivitesi sayesinde gerçekleşir.

Bu yazıda Workflow Foundation içerisindeki, bizim de programlama dillerinden bildiğimiz ifadelerin aktivitelerini inceledik.

WF 4.0 ile Tasarım Kısmına Daha Derin Bir Bakış

Bir önceki yazımda gördüğünüz üzere, bu kadar basit bir workflow’da bile tüm diagramı görmek ne kadar zor durumlar alabiliyor. İyi ki, tasarım kısmı büyük workflow’larda çalışabilmemiz için bize bir çok özellik sağlıyor. Örneğin; tasarım kısmının sağ üst köşesindeki “Expand All” ve “Collapse All” seçenekleri ile bize görünüm anlamında çok büyük avantajlar sağlar. Collapse All seçeneği ile tüm ana aktiviteleri görmemize olanak verir. Expand All seçeneği ile de tüm aktiviteleri genişletir fakat bu durumda tüm diagramın bir kısmını görebiliriz.

Sağ alt köşedeki “Overview” seçeneğine tıklarsak, tüm diagramı küçük bir pencerede görebiliriz. Bu pencerenin içindeki sarı kutu, bize o anda diagramın içerisinde gösterilmek istenen alanı verir. Bu sarı kutuyu, hangi kısmı görmek istiyorsak o kısma sürükleyebiliriz.

WF-Designer

Şimdi Overview seçeneğini kapatıp hemen yanındaki “Fit to screen” özelliğine bakalım. Bu özellik bize tüm diagramı mümkün olduğunda küçültüp tasarım penceresine sığdırır. Monitör büyüklüğünüze bağlı olarak bu şekilde aktiviteleri okumak zorlaşabilir. Eğer zoom özelliğinin yanındaki büyüteç işaretine tıklarsak bu durumda diagram %100 haline gelip eski haline bu şekilde döndürebiliriz.

Şimdi biraz daha derinlere bakalım;

Solution Explorer kısmından “Workflow1.xaml” dosyamıza sağ tıklayıp “View Code” seçeneğine tıklayalım. Bu adımda bu dosyanın hala açık olduğunu, kapatmak isteyip istemediğinizi soran bir uyarı mesajı alacaksınız. Bu uyarıyı evet diyerek geçelim. Aşağıdaki şekilde .xaml doyamızın kod kısmını göreceğiz;


<Activity mc:Ignorable="sap" x:Class="WF_Temel_Elemanları_Eklemek.Workflow1" sap:VirtualizedContainerService.HintSize="526,1297" mva:VisualBasic.Settings="Assembly references and imported namespaces for internal implementation" xmlns="<a href="http://schemas.microsoft.com/netfx/2009/xaml/activities&quot;">http://schemas.microsoft.com/netfx/2009/xaml/activities"</a> xmlns:mc="<a href="http://schemas.openxmlformats.org/markup-compatibility/2006&quot;">http://schemas.openxmlformats.org/markup-compatibility/2006"</a> xmlns:mv="clr-namespace:Microsoft.VisualBasic;assembly=System" xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:s1="clr-namespace:System;assembly=System" xmlns:s2="clr-namespace:System;assembly=System.Xml" xmlns:s3="clr-namespace:System;assembly=System.Core" xmlns:s4="clr-namespace:System;assembly=System.ServiceModel" xmlns:sa="clr-namespace:System.Activities;assembly=System.Activities" xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:sap="<a href="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation&quot;">http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation"</a> xmlns:scg="clr-namespace:System.Collections.Generic;assembly=System" xmlns:scg1="clr-namespace:System.Collections.Generic;assembly=System.ServiceModel" xmlns:scg2="clr-namespace:System.Collections.Generic;assembly=System.Core" xmlns:scg3="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:sd="clr-namespace:System.Data;assembly=System.Data" xmlns:sl="clr-namespace:System.Linq;assembly=System.Core" xmlns:st="clr-namespace:System.Text;assembly=mscorlib" xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml&quot;">http://schemas.microsoft.com/winfx/2006/xaml"</a>>
<Sequence sad:XamlDebuggerXmlReader.FileName="c:\users\soner\documents\visual studio 2010\Projects\WF-Temel Elemanları Eklemek\WF-Temel Elemanları Eklemek\Workflow1.xaml" sap:VirtualizedContainerService.HintSize="486,1257">
<Sequence.Variables>
<strong><Variable x:TypeArguments="x:Int32" Name="Counter" />
</strong>      <strong><Variable x:TypeArguments="x:Int32" Default="[DateAndTime.Now.Hour]" Name="NumberOfBells" /></strong>
</Sequence.Variables>
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<WriteLine DisplayName="Merhaba" sap:VirtualizedContainerService.HintSize="464,59" />
<strong><If Condition="[NumberOfBells &gt; 12]" DisplayName="PM için ayarla" sap:VirtualizedContainerService.HintSize="464,201">
</strong>      <sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
<x:Boolean x:Key="IsPinned">False</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<If.Then>
<Assign sap:VirtualizedContainerService.HintSize="291,100">
<Assign.To>
<OutArgument x:TypeArguments="x:Int32">[NumberOfBells]</OutArgument>
</Assign.To>
<Assign.Value>
<InArgument x:TypeArguments="x:Int32">[NumberOfBells - 12]</InArgument>
</Assign.Value>
</Assign>
</If.Then>
</If>
<strong><While DisplayName="Zil Çalınma Sayısı" sap:VirtualizedContainerService.HintSize="464,453">
</strong>      <sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
<x:Boolean x:Key="IsPinned">False</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<strong><While.Condition>[Counter &lt;= NumberOfBells]</While.Condition>
</strong>      <strong><Sequence DisplayName="Zil Çalınma Sayısı" sap:VirtualizedContainerService.HintSize="438,342">
</strong>        <sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
<x:Boolean x:Key="IsPinned">False</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<strong><WriteLine sap:VirtualizedContainerService.HintSize="242,59" Text="[Counter.ToString()]" /></strong>
<Assign sap:VirtualizedContainerService.HintSize="242,57">
<strong><Assign.To>
<OutArgument x:TypeArguments="x:Int32">[Counter]</OutArgument>
</Assign.To>
<Assign.Value>
<InArgument x:TypeArguments="x:Int32">[Counter + 1]</InArgument>
</Assign.Value>
</strong>        </Assign>
<strong><Delay Duration="[TimeSpan.FromSeconds(1)]" sap:VirtualizedContainerService.HintSize="242,22" />
</strong>      </Sequence>
</While>
<strong><WriteLine DisplayName="Zaman:" sap:VirtualizedContainerService.HintSize="464,59" Text="[&quot;Şu anki zaman: &quot; + DateTime.Now.ToString()]" /></strong>
<strong><If Condition="[DateTime.Now.Hour &gt;= 18]" DisplayName="Selamlama" sap:VirtualizedContainerService.HintSize="464,201">
</strong>      <If.Then>
<strong><WriteLine sap:VirtualizedContainerService.HintSize="219,100" Text="İyi Akşamlar" />
</strong>      </If.Then>
<If.Else>
<strong><WriteLine sap:VirtualizedContainerService.HintSize="220,100" Text="İyi Günler" /></strong>
</If.Else>
</If>
</Sequence>
</Activity>

Burada gördüğünüz üzere aktiviteleri bold olarak işaretledim. Ayrıca burdaki koda baktığımızda hiç çalıştırılabilir bir kod göremeyiz. Bu .xaml dosyası içerisinde sadece iç içe yuvarlanmış özellikler görüyoruz. Normalde Counter değişkeni için Counter = Counter + 1 şeklinde bir kod satırı görmeliydik. Bunun yerine elimizde bir Assign sınıfı ve bu sınıf içerisinde Counter ve Counter + 1 ifadeleri mevcut. Burada aslında Assign sınıfı içerisinde bu ifadeler çalıştırılır. Workflow tanımında herhangi bir ifade belirtmezler.

Workflow Foundation 4.0’a Giriş

.NET 4.0 içerisinden Workflow Foundation (ayrıca WF 4.0 olarak anılır) workflow tabanlı uygulamaların geliştirilmesi için yeni bir örnek ifade eder.

Şimdi basit bir workflow oluşturma ile işe başlayalım. Visual Studio 2010’u çalıştırıp New Project linkine tıklayalım. Installed Templates’in altında, Visual C#’ın içerisinde, 4 adet farklı template’in var olduğunu göreceğiz. Buradan Windows Console Application’ı seçelim ve projemizin ismini HelloWF olarak adlandıralım.

HelloWF

Bu adım bize bir adet konsol uygulaması için Program.cs  ve Workflow1.xaml adında workflow’unuzdaki aktiviteleri tanımlayan dosyalar üretilir. XAML dosyası, iş akışı tanımınızdaki aktivite-türetilen elementleri içerir. Visual Studio 2010 size bu tür elementleri düzenlemeniz için grafiksel olarak bir arayüz imkanı tanır. Burada klasik bir VS 2010 arayüzünü görüyoruz. Sağ tarafta Solution Explorer, Properties, sol tarafta Toolbox, Server Explorer, alt tarafta ise Errors, Warnings ve Messages kısımlarını görmekteyiz (Tabi bu kısımların IDE içerisinde yerlerinin kişiden kişiye değişebileceğini unutmayalım.)

WF-IDE

Orta tarafta ise WF tasarım kısmını görüyoruz. Sağ altta zoom için gerekli olan seçenekleri ve Overview seçeneklerini görüyoruz. Zira Workflow Foundation içerisinde oluşturabileceğiniz aktiviteler çok büyük olabileceği için bu kontroller bize büyük resim içerisinde küçük resmi görebilmek için çok büyük avantaj sağlayacaklardır. Sol alt tarafta ise 3 adet kontrolümüz var. “Variables”, “Arguments” ve assembly dosyalarını içeren “Imports”. Variables kısmına tıkladığımızda var olan değişkenleri içeren bir pencere açılacaktır.

WF-Variables

Eğer oluşturduğunuz workflow’u bir sınıf olarak düşünürsek, variables kısmı sınıf üyeleri olarak düşünebiliriz. Bunlarla aktiviteler arasındaki paylaşılan verileri tutabilirsiniz. Bu değişkenleri isterseniz tüm workflow için, isterseniz belirli bir aktivite için tanımlayabilirsiniz. Arguments kısmı tıpkı değişkenler gibidir, farklı olarak workflow dışına veri geçisini sağlayabilirler. Bunları method parametreleri olarak düşünebiliriz. Not olarak; buradaki Direction kolonu, verinin nereye geçtiğini ya da workflow dışında nereye gönderildiğini gösterir.

WF-Arguments

 

~Workflow Tasarımı~

Workflow’u ilk oluşturduğumuzda tasarım kısmının boş olduğunu görürüz. Aktiviteleri sürükle bırak mantığı ile workflow’umuza ekleriz. Şimdi Workflow Foundation içerisinde küçük bir “Hello World” uygulaması yaratalım. Başlangıç için, Sequence aktivitesini tasarım alanına sürükleyip bunun içerisinde Writeline aktivitesini sürükleyelim. Diagram aşağıdaki şekilde görünecektir;

WF-Sequence-Writeline

Ayrıca Properties kısmı aşağıdaki şekilde görünür.

WF-Properties

Buradaki DisplayName özelliğini Hello şeklinde değiştirelim ki, bir çok Writeline aktivitemizin olduğu diagramlarda bu yapıları hatırlamamız kolaylaşsın. Ayrıca Text özelliğine “Hello, World!” kelimelerini giriş yapalım. Tasarım kısmında da aynı anda Writeline aktivitesinin içerisinde bu stringi görebiliriz. TextWriter özelliğini boş bırakabiliriz. Default olarak, bu text konsola yazılacaktır. Eğer spesifik bir uygulama yapmak isterseniz buraya TextWriter’dan türetilmiş sınıf yapılarını da yazabiliriz. Şimdi de Program.cs dosyamızda değişiklikler yapalım. Bu dosyayı ilk açtığımızda aşağıdaki şekilde görürüz;


using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;

namespace HelloWF
{

class Program
{
static void Main(string[] args)
{
WorkflowInvoker.Invoke(new Workflow1());
}
}
}

Statik WorkflowInvoker sınıfı Workflow1 sınıfı tarafından tanımlanmış workflow’u başlatır. Şimdi Main() methodumuzun içerisine aşağıdaki kodları ekleyelim;

Console.WriteLine(“ENTER to exit”);
Console.ReadLine();

Daha sonra projemizi F5 ile çalıştırdığımızda, aşağıdaki şekilde bir konsol çıktımız olur.

Hello, World!
ENTER to exit

Bu makalede Windows Workflow Foundation’a giriş kısmını anlatmaya çalıştım. Umarım bundan sonra WF 4.0 ile ilgili açıklayıcı makaleler yazabilirim.

C#’ta Fonksiyon Üyeleri

C#’ta çalıştırılabilir kod içeren üyeler genellikle sınıfın fonksiyon üyeleri olarak adlandırılırlar. Bir önceki yazıda belirttiğim konu olan methodlar konusu da aslında bir çeşit fonksiyon üyesidir. Aşağıda tablo Liste<T> olarak tanımlanmış bir generic sınıfın bir çok fonksiyon üyesi örneğini gösterir:

public class Liste<T> {
const int varsayılanKapasite = 4; Sabit
T[ ] elemanlar;
int sayaç;
Alanlar
public Liste (int kapasite = varsayılanKapasite ) {
elemanlar = new T[kapasite];
}
Yapıcı
public int Sayma {
get { return sayma; }
}
public int Kapasite {
get {
return elemanlar.Length;
}
set {
if (value < sayma) value = sayma;
if (value != elemanlar.Length) {
T[] yenielemanlar= new T[value];
Array.Copy(elemanlar, 0, yenielemanlar, 0, sayma);
elemanlar = yenielemanlar;
}
}
}
Özellikler
public T this[int index] {
get {
return elemanlar[index];
}
set {
elemanlar[index] = value;
OnChanged();
}
}
İndexleyiciler
public void Add(T item) {
if (sayma == kapasite) kapasite = sayma * 2;
elemanlar[sayma] = item;
sayma++;
OnChanged();
}
Methodlar
public event EventHandler Changed; Olaylar (Events)
public static bool operator ==(Liste<T> a, Liste<T> b) {
return Equals(a, b);
}
public static bool operator !=(Liset<T> a, Liste<T> b) {
return !Equals(a, b);
}
Operatörler
}
  • Yapıcılar (Constructors)

C# hem örnek hem de static yapıcıları destekler. Örnek yapıcılar, sınıfın ilk kullanımına hazırlamak için gerekli olan uygulama üyeleridir. Static yapıcılar, bir eylem gereken örnek sınıfın ilk yüklendiği zaman gerekli olan uygulama üyeleridir.

Bir yapıcı aynı bir method gibi tanımlanır fakat geri dönüş tipi olmamalı ve içerdiği sınıf ile aynı isme sahip olmalıdır. Eğer bir yapıcı, static anahtar kelimesi ile tanımlanmışsa o static yapıcıdır, aksi taktirde örnek yapıcı olarak adlandırılırlar.

Örnek yapıcılar aşırı yüklenebilir. Örneğin, Liste<T> adında bir sınıfın iki yapıcısı tanımlansın; birisini hiçbir parametre almayan, diğeri de bir adet int parametre alan. Örnek yapıcılar new operatörü kullanılarak çağırılırlar.


Liste<string> liste1 = new Liste<string>();
Liste<string> liste2 = new Liste<string>(10);

Diğer üyelerin aksine, örnek methodlar miras almazlar. Yani bir sınıfın kendi içinde tanıtılmış örnek yapıcısından başka bir örnek yapıcısı olamaz. Eğer bir sınıfın örnek yapıcısı yoksa, parametresiz boş bir örnek yapıcı otomatik olarak yaratılır.

Önemli: Yapıcılar tembel olmalıdırlar. En iyi pratik olarak yapıcılar içinde minimum iş yapılamıdır ki bu da basit bir şekilde argümanların daha sonra kullanımı için basitleştirmelidir. Örneğin; bir dosyanın ismini ya da o dosyanın yolunu veritabanı için tutmak istiyorsunuz ama dış kaynakları gerekli olmadığı sürece açmak istemiyorsunuz. Bu pratik, gerekli en az kaynağa erişimede emin olmak için yardımcı olur.

  • Özellikler (Properties)

C#’ta özellikler, alanların (fields) doğal uzantılarıdır. İkisi de ilişkilendirildikleri tiplerile birlikteki üyelerin isimleriyle, ilişkilendirildikleri alanların sözdizimi ile ve özelliklerle isimlendirilirler. Fakat, alanlardan farklı olarak, özellikler depolanmış bellek göstermezler. Bunun yerine, özelliklerin erişimcileri vardır ki onların değerlerini okuma ya da yazmada kullanılan ifadeleri belirtmede kullanılırlar.

Önemli: İyi bir uygulama tekniği olarak alanlara (özelliklerin verilerine) her zaman Getter ve Setter ile ulaşmak önerilir. Eğer Getter veya Setter’larınıza fonksiyonel işlevsellikler eklemek isterseniz, kodunuzu herhangi bir şekeilde kırmadan bunu kolaylıkla yapabilirsiniz.

Yukarıdaki anlatıma bir örnek verelim: Hiçbir zaman alanlarınızı (fields) public olarak tanımlamayın;


class İnek
{
public int Süt; // Kötü
}

Eğer belleğin yanında herhangi bir katman istemiyorsanız, bırakın derleyici sizin yerinize uygulasın;


class İnek
{
public int Süt {get; set;} // İyi
}

Bu sayede, istemci özelliğe Getter ve Setter’ı bağlar ve buna istinaden aşağıdaki şekilde derleyicinin uygulamasına etki edebilirsiniz;


class İnek
{
bool SüteSahip = false;
int Süt;
public int Süt {
get
{
if (!SüteSahip)
{
Süt = SütMakinesiUygula();
SüteSahip = true;
}
return Süt;
}
set
{
TersSütMakinesiUygula(value);
Süt = value;
}
}
}

Ayrıca, aşağıdaki kullanım ile hesaplanmış değer sizin programınızda kullanılır;


class İnek
{
public İnek()
{
Süt = SütMakinesiUygula();
}
public int Süt { get; private set; }
...
}

Bu durumda, özelliği tekrar hesaplıyoruz, ki neye ihtiyacımız olduğunu bilmediğimiz durumlarda bu gereksiz bir davranıştır.

Önemli: Özelliklere erişim tıpkı alanlara erişim gibi gözükür insanlara. Ve kişiler de doğal olarak alanlara erişim ile aynı performans göstereceklerini tahmin ederler. Eğer bir erişim belirgin bir iş ihtiyacı duyuyorsa (bir dosyadan okuma, veritabanında bir query çalıştırma gibi) bir method olarak açılmalıdır, bir özellik olarak değil. Bunu çağıranlar methodun daha fazla iş yaptığını düşünürler.

Aynı sebepten dolayı, özelliklere erişimde tekrarlı çağırımlar (koda müdahele etmeden) aynı değeri döndürmelidirler. DateTime.Now bu tavsiyeye uymayan örneklere sahiptir.

Bir özellik, bir alan ile aynı şekilde tanımlanır. İstisna olarak, noktalı virgülle bitirmek yerine Get ve Set erişimcilerini { ve } arasında kullanarak tanımsal biçimde alanlardan ayrılırlar. Hem Get hem de Set erişimcisine sahip özellikler okunur-yazılır (read-write) özellik, sadece Get erişimcisine sahip özellikler sadece okunur (read-only) ve sadece Set erişimcisine sahip özellikler de sadece yazılır (write-only) özellikler olarak tanımlanır.

Bir Get erişimcisi parametresiz bir method ve özellik tipini geri dönüş tipi olarak uygulayan bir method ile uyuşur. Belirlenen hedefin atamalar dışında, bir özellik bir ifadeyi referans ediyorsa, özelliğin Get erişimcisi, özelliğin değerini hesaplamada çağrılır.

Bir Set erişimcisi, value adında bir parametre içeren ve geri dönüş tipi olmayan bir method ile uyuşur. Bir özellik belirli bir atamayı ya da ++ ya da – gibi operandları referans ediyorsa, Set erişimcisi yeni değeri içeren bir argüman ile çağrılır.

Liste<T> sınıfı iki adet özellik tanımlar, Sayma ve Kapasite, sırasıyla sadece okunur ve okunur-yazılır özellikler. Aşağıdaki örnek bunların nasıl kullanıldığına bir örnektir:


Liste<string> isimler = new Liste<string>();
isimler.Kapasite = 100;   // Set erişimcisi çağrılır.
int i = isimler.Sayma;      // Get erişimcisi çağrılır.
int j = isimler.Kapasite;  // Get erişimcisi çağrılır.

Alanlarda ve özelliklerde benzer olarak, C# hem örnek özellikleri hem de static özellikleri destekler. Static özellikler static anahtar kelimesi ile tanımlanır, static anahtar sözcüğü kullanılmayan özellikler örnek özelliklerdir.

Bir özelliğin erişimcileri (Get ve Set) sanal (virtual) olabilirler. Eğer bir özellik tanımlaması virtual, abstract ya da override anahtar sözcüklerine sahipse, o özelliğin erişimcilerine uygular.

Önemli: Eğer bir sanal özellik, private erişimciye sahipse, o erişimci CLR içinde sanal olmayan methodtur ve türetilmiş sınıf içerisinde aşırı yüklenemez.

Özellikler aşağıdaki erişimcilere sahiptirler;

Statik değiştirici static
Erişim değiştirici public, internal, private, protected
Kalıtımsal değiştirici new, virtual, abstract, override, sealed
Yönetilemeyen kod değiştiricisi unsafe, extern
  • İndexleyiciler (İndexers)
    İndexleyiciler, diziler gibi, nesneleri indexleyebilen üyelerdir. Bir indexleyici, bir özellik ile aynı şekilde tanımlanır, tek fark olarak [ ] arasında parametre listesi hariç. Bu parametreler, indexleyicinin erişimcisi için uygundurlar. Özellikler gibi, indexleyiciler de read-write, read-only, write-only ya da bir indexleyici virtual tanımlanabilir.

Aşağıdaki List sınıfı string parametresi olan basit bir indexleyici tanımlamıştır;


List<string> isimler = new List<string>();
isimler.Add("Ülkü");
isimler.Add("Hülya");
isimler.Add("İlknur");
for (int i = 0; i < isimler.Count; i++) {
string s = isimler[i];
isimler[i] = s.ToUpper();
}

İndexleyiciler aşırı yüklenebilirler. Bunun anlamı sınıflar istedikleri uzunlukta parametre listeleri içeren indexleyici tanımlayabilirler.

  • Olaylar (Events)

Bir olay, sınıflara ya da nesnelere bildirim sağlanmasına olanak veren üyelerdir. Olaylar, tıpkı alanlar gibi tanımlanırlar. Fark olarak event anahtar kelimesi kullanılır ve tipi delegate tipi olmalıdır.

Jesse Liberty: Gerçekte, event anahtar sözcüğü sadece C#’ta delegate’in kullanılabilir olmasına bir yol göstericidir.

Chris Sells: event anahtar sözcüksüz, şunu yapabilirsiniz;


delegate void CalismaTamamlandi();
class Calisan
{
public  CalismaTamamlandi Tamamlandi; // Delegate alanı, bir event değil.

}
class  Patron
{
public void CalismaTamamlandi() { }
}
class Program
{
static void Main()
{
Patron Soner = new Patron();
Calisan isci = new Calisan();
isci.Tamamlandi += Soner.CalismaTamamlandi(); // Yapmak istediğiniz
isci.Tamamlandi = Soner.CalismaTamamlandi();  // Derleyicinin izin verdiği
}
}

Ne yazık ki, event sözcüğü ile birlikte, Tamamlandi() sadece delegate tipinde public bir alandır. Derleyici için de sorun oluşturmaz bu durum. Fakat event anahtar sözcüğünü eklediğimizde, += ve –= gibi operatörleri sınırlandırırsınız.


class Calisan
{
public event CalismaTamamlandi Tamamlandi;

}

isci.Tamamlandi += Soner.CalismaTamamlandi(); // Derleyici izin verir.
isci.Tamamlandi = Soner.CalismaTamamlandi(); // Derleyici hata verir.

Burada event sözcüğünün kullanımı sadece bir kere public bir alan oluşturmanın doğaln olmasıdır.Çünkü derleyici güvenli operatörlerin kullanımını daraltır.

Olay üyesi tanımlayan bir sınıf içerisinde, olaya delegate tipindeki bir alana erişimi gibi erişilebilir. Alan, olaylara eklenen olay tutucuları (event handler) temsil eden delegate’e bir referans saklar. Eğer herhangi bir event handler mevcut değilse, o alanın değeri null olur.

List<T> sınıfı basit bir olay üyesi olan Changed tanımlar ki bu yeni elemanların listeye eklenmesini gösterir. Changed olayı, OnChanged sanal methodundan kullanılır ki bu ilk önce olayın boş olup olmadığını kontrol eder. (Bunun anlamı hiçbir event handler’ın olmaması)

İstemciler olaylara olay tutucular (event handler) ile tepki gösterirler. Event handler’lar += operatörü ile eklenir ve –= operatörü ile silinebilirler. Aşağıdaki örnek, bir event handler’ın List<string>’in Changed olayına eklenmesini gösterir;


using System;
using System.Collections.Generic;

class Test
{
static int DegismeSayisi;
static void ListChanged(object sender, EventArgs e)
{
DegismeSayisi++;
}
static void Main()
{
List<string> isimler = new List<string>();
isimler.Changed += new EventHandler(ListChanged);
isimler.Add("Ülkü");
isimler.Add("Hülya");
isimler.Add("İlknur");
Console.WriteLine(DegismeSayisi); // Çıktı 3 olur.
}
}

Chris Sells: isimler.Changed += new EventHandler(ListChanged); yerine isimler.Changed += ListChanged; şeklinde bir kısaltma da kullanılabilir.

  • Yok Ediciler (Destructors)

Bir destructor, bir sınıf örneğini ya da nesnesini yoketmek için kullanılan uygulamaları içerir. Yokedicilerin parametreleri yoktur, erişim kelimelerine sahip değillerdir ve açık bir şekilde çağrılamazlar. Yokediciler otomatik olarak garbage collection zamanında çalıştırılırlar.

Garbage collector, nesnelerin ne zaman toplanacağına ve yokedicilerin ne zaman çalıştırılacağına imkan verir. Özellikle, yokedicilerin çağrılma zamanların rastgele değildir, ve yokediciler herhangi bir thread zamanında çalıştırılabilir. Bu nedenle, sınıflar yokedicileri  sadece başka çözümler olmadığı zamanda uygulamalıdırlar.

Önemli: Yokediciler bazen “Finalizer” olarak anılırlar. Bu isim aynı zamanda Garbage collector API’sinde de görünür; örneğin: GC.WaitForPendingFinalizers.

C#’ta Interface (Arayüz) Kavramı

C#’ta arayüzler, sınıf ve struct yapılarının ne yapması gerektiğini belirten yapılardır. Bir arayüz; method, özellik (properties), olay (event) ve indexleyici (indexers) yapılarını içerebilir. Bir arayüzün içindeki metodların gövdeleri yoktur. Yani bir arayüz hiçbir zaman hiç bir şekilde bir uygulama sağlamaz. Bir arayüzü birden fazla sınıf ya da struct kullanabildiği gibi, bir sınıf ya da struct yapısı da birden fazla arayüz uygulayabilirler.

Arayüzler çoklu kalıtımı (multiple inheritance) desteklerler. Örneğin; aşağıdaki ICombobox arayüzü hem ITextBox hem de IListBox arayüzlerinden kalıtım alır:

interface IControl 
{ 
    void Boyama(); 
} 
interface ITextBox : IControl 
{ 
    void YazıBelirler(string text); 
} 
interface IListBox : IControl 
{ 
    void OgeBelirle(string[] items); 
} 
interface IComboBox : ITextBox, IListBox { }

Sınıflar ve struct yapıları da çoklu kalıtımı desteklerler. Aşağıda, EditBox sınıfı hem IControl hem de IDataBound arayüzlerinden kalıtım alır:

interface IDataBound 
{ 
    void Bind(); 
} 
public class EditBox : IControl, IDataBound 
{ 
    public void Boyama() { } 
    public void Bind() {}

}

Bir sınıf ya da struct belirli bir arayüzü uyguladığında, o sınıf ya da struct örneği, dolaylı olarak arayüz tipine çevrilebilir. Örneğin;

EditBox editBox = new EditBox(); 
IControl control = editBox; 
IDataBound dataBound = editBox;

Arayüzler aşağıdaki nedenlerden dolayı özeldirler;

  • Bir sınıf birden fazla arayüz uygulayabilir. Buna karşılık, bir sınıf sadece bir sınıftan kalıtım alabilir.
  • Arayüz üyelerinin tümü dolaylı olarak soyuttur (abstract). Buna karşılık, bir sınıf hem soyut hem de somut üyelerin uygulanmasına olanak sağlar.
  • Struct yapıların arayüzleri uygulayabilir. Buna karşılık, bir struct yapısı bir sınıf yapısından kalıtım alamaz.

Bir arayüz tanımlanması tıpkı sınıf tanımlamasına benzer, fakat bir arayüz kendi üyelerinin hepsi dolaylı olarak soyut oldukları için bunlara herhangi bir uygulama sağlamaz. Bu üyeler, bu arayüzü uygulayan sınıf ya da struct yapıları tarafından kullanılırlar.

System.Collections namespace’inde bulunan IEnumerator arayüzünün tanımı şu şekildedir;

public interface IEnumerator 
    { 
        bool MoveNext(); 
        object Current { get; } 
        void Reset(); 
    }

Bir arayüz üyesi her zaman dolaylı olarak public özelliktedir ve herhangi bir erişim değiştiricisi tanımlanamaz. Bir arayüzü uygulamak onun tüm elemanlarını public olarak kullanılmasına olanak sağlamak anlamına gelir.

internal class GeriSayim : IEnumerable 
{ 
    int sayac = 10; 
    public bool MoveNext() { return sayac-- > 0; } 
    public object Current { get { return sayac; } } 
    public void Reset() { throw new NotSupportedException(); } 

}

Dolaylı olarak bir nesneyi herhangi bir arayüz tipine çevirebilirsiniz;

IEnumerator e = new Countdown();

Bir arayüz başka bir arayüzden türetilebilir. Bu sayede türetilen arayüz diğer arayüzün tüm üyelerini kalıtım alır. Örneğin;

public interface IGeriAl 
     { 
         void GeriAl(); 
     } 
     public interface IYenidenYap : IGeriAl 
     { 
         void YenidenYap(); 
     }

Burada IYenidenYap, IGeriAl arayüzünün tüm üyelerini kalıtım alır.

Açık Arayüz Uygulaması (Explicit Interface Implementation)

      Birden fazla arayüz uygulaması bazen üye imzalarının çarpışması sonuçları doğurabilir. Bu durumu arayüz üyesine uygulanan

açıkça uygulama(explicitly implementing)

    şeklinde çözebilirsiniz. Şu aşağıdaki koda göz atalım;
class Test 
{ 
    interface I1 
    { 
        void Foo(); 
    } 
    interface I2 
    { 
        int Foo(); 
    } 
    public class Arac : I1, I2 
    { 
        public void Foo() 
        { 
            Console.WriteLine("I1.Foo uygulamasi"); 
        }

        int I2.Foo() 
        { 
            Console.WriteLine("I2.Foo uygulaması"); 
            return 23; 
        } 

    } 
}

Görüldüğü üzere, hem I1 hem de I2 arayüzlerinin çelişkili Foo imzaları var. Arac sınıfı, açık bir şekilde I2 arayüzünün Foo methodunu uygular. Bu durum bu iki methodun aynı sınıf içerisinde bir arada olmasına olanak verir. Bu üyelerin çağrımını ancak arayüz çevrimleri ile sağlayabiliriz:

Arac a = new Arac(); 
a.Foo();       // I1.Foo uygulanır. 
(I1)a.Foo(); // I1.Foo uygulanır. 
(I2)a.Foo(); // I2.Foo uygulanır.

Arayüz Üyelerini Sanal Uygulama (Implementing Interface Members Virtually)

Açıkça uygulanmış arayüz üyeleri default olarak sealed tanımlıdır. Temel sınıfta bu üyeler virtual ya da abstract olarak tanımlanmak zorundadır. Örneğin;

using System; 
using System.Collections; 
class Test 
{ 
    public interface IGeriAl 
    { 
        void GeriAl(); 
    } 
    public class Sınıf1 : IGeriAl 
    { 
        public virtual void GeriAl() 
        { 
            Console.WriteLine("Sınıf1.GeriAl()"); 
        } 
    } 
    public class Sınıf2 : Sınıf1 
    { 
        public override void GeriAl() 
        { 
            Console.WriteLine("Sınıf2.GeriAl()"); 
        } 
    } 
    public static void Main() 
    { 
        Sınıf2 s = new Sınıf2(); 
        s.GeriAl();                  // Sınıf2.GeriAl() 
        ((IGeriAl)s).GeriAl();  // Sınıf2.GeriAl() 
        ((Sınıf1)s).GeriAl();    // Sınıf2.GeriAl() 
    } 
}

Bir arayüz elemanını temel sınıftan doğrudan çağırmak ya da alt sınıftan arayüz çağrısı ile çağırmak bize aynı sonucu verir.

Altsınıflarda Arayüzü Tekrar Uygulama

Bir altsınıf, temel sınıf tarafından uygulanmış arayüz üyelerini tekrar tekrar uygulayabilir. Tekrar uygulama, üye uygulamasını kaçırır ve temel sınıftaki üyeler virtual olsun ya da olmasın çalışır. Aşağıdaki örnek ile daha iyi anlatmaya çalışalım;

Bu örnekte; TextBox sınıfı IGeriAl.GeriAl() methodunu açıkça uygular. Bu yüzden virtual olarak işaretlenemezler. Override etmek için, RichTextBox sınıfı IGeriAl.GeriAl() methodunu yeniden uygulamak zorundadır.

public interface IGeriAl 
    { 
        void GeriAl(); 
    } 
    public class TextBox : IGeriAl 
    { 
        void IGeriAl.GeriAl() 
        { 
            Console.WriteLine("TextBox.GeriAl"); 
        } 
    } 
    public class RichTextBox : TextBox, IGeriAl 
    { 
        public new void GeriAl() 
        { 
            Console.WriteLine("RichTextBox.GeriAl"); 
        } 

    }

Altsınıfın uygulamasında arayüz çağrısı olarak tekrar uygulanmış üyeleri çağırmak için;

RichTextBox r = new RichTextBox(); 
r.GeriAl(); //RichTextBox.GeriAl 
((IGeriAl)r).GeriAl();  //RichTextBox.GeriAl

Peki ya TextBox sınıfımızı aşağıdaki şekilde değiştirirsek?

public class TextBox : IGeriAl 
    { 
        public void GeriAl() 
        { 
            Console.WriteLine("TextBox.GeriAl"); 
        }

    }

Bu özellik bize GeriAl() methodunu farklı bir şekilde çağırma olanağı sağlar.

RichTextBox r = new RichTextBox(); 
r.GeriAl();                   // RichTextBox.GeriAl 
((IGeriAl)r).GeriAl();    // RichTextBox.GeriAl 
((TextBox)r).GeriAl();  // TextBox.GeriAl

Bu üçüncü durum bize gösteriyor ki, bu durumda bir uygulama yapılması bir üyenin base sınıftan değil de direkt arayüz tarafından çağrılması durumlarında etkin hale gelir. Bu genellikle istenilmeyen bir durumdur.

Arayüzler ve Boxing

Bir struct yapısını bir arayüz yapısına çevirmek boxing işlemidir. Dolaylı olarak uygulanmış struct yapısını çağırmak boxing işlemi değildir.

         interface I 
        { 
            void Metod(); 
        }

        struct S : I 
        { 
            public void Metod() { } 
        }

        S str = new S(); 
        str.Metod(); //Boxing değil

        I i = S; 
        i.Metod(); //Arayüz yapısına çevirdiğimizde boxing gerçekleşir.

Sınıf vs. Arayüz Yazılması

Temel olarak;

  • Uygulama aşamasında doğal olarak paylaşıllan tipler için sınıf ya da alt sınıflar kullanın.
  • Bağımsız uygulamaya sahip tipler için arayüzler kullanın.

Aşağıdaki sınıflar göz önüne alalım;

    abstract class Hayvan { } 
    abstract class Kus : Hayvan { } 
    abstract class Bocek : Hayvan { } 
    abstract class Kusgiller : Hayvan { } 
    abstract class Etobur : Hayvan { }

    //Somut Sınıflar

    class DeveKusu : Kus { } 
    class Kartal : Kus, Kusgiller, Etobur { } //Geçersiz 
    class Arı : Bocek, Kusgiller { } //Geçersiz 
    class Pire : Bocek, Etobur { } //Geçersiz

Kartal, Arı ve Pire sınıfları derlenmezler çünkü birden fazla sınıftan kalıtım almaları yasaktır. Bunu çözmek için, bazı tipleri arayüz tipine çevirmek zorundayız. Tabi burada soru şudur, hangi tipleri? Genel kuralımızı takip ederek, Bocek ve Kus sınıfları uygulamalarını paylaşırlar. Bu yüzden bunlar sınıf olarak kalmak zorundadır. Buna karşılık, Kusgiller uçmak için bağımsız bir mekanizmaya sahiptir ve Etobur et yiyenler için bağımsız stratejilere sahiptir. Bu yüzden bu sınıfları arayüz tipine çevirmemiz gerekir.

interface IKusgiller { } 
interface IEtobur { }

C#’ta Enum Tipine Genel Bakış

C#’ta enum tipi, sabitlerle isimlendirilmiş ayırt edici veri tipidir. Aşağıdaki örnek 3 sabit değer ile oluşturulmuş enum tipi olan Renk adında bir yapı tanımlayalım:

using System;

namespace ConsoleApplication1
{

enum Renk
 {
 Kırmızı,
 Yeşil,
 Mavi
 }

class Test
 {
 static void RenkBastır(Renk renk)
 {
 switch (renk)
 {
 case Renk.Kırmızı:
 Console.WriteLine("Kırmızı");
 break;
 case Renk.Yeşil:
 Console.WriteLine("Yeşil");
 break;
 case Renk.Mavi:
 Console.WriteLine("Mavi");
 break;
 default:
 Console.WriteLine("Bilinmeyen renk");
 break;
 }
 }

static void Main()
 {
 Renk r = Renk.Kırmızı;
 RenkBastır(r);
 RenkBastır(Renk.Mavi);
 }
 }
}
 

Her enum tipi underlying type adı verilen bir enum tipi ile ilişkilendirilir. Bir enum, underlying type içerisinde formatını ve mümkün olan değerin uzunluğunu tutar. Varsayılan olarak;

  • Her underlying değerleri int tipindedir.
  • Sabitler 0, 1, 2, 3.. enum üyelerinin tanımlama sıralarına göre otomatik olarak atanır.

Aşağıdaki örnek, sbyte tipinde bir underlying type ile Hiza adından bir enum tipini tanımlar;

enum Hiza : sbyte
 {
    Sol = -1,
    Orta = 0,
    Sağ = 1
 } 

Bu örnekte de görüldüğü gibi, bir enum üyesi tanımlama aşamasında sabit bir değere atanabilir. Burada atadığımız her sabitin underlying type (sbyte) tipinin aralığı içerisinde bulunmak zorundadır. Eğer bir enum tipi tanımlamasında açıkça belirli bir değere atanmamışsa, ilk değeri belirtmek için 0 (sıfır) değeri verilir ve tanımlama sıralamasına göre 1 artarak ilerler.


int i = (int)Renk.Mavi; // int i = 2;
Renk r = (Renk)2; // Renk r = Renk.Mavi

Enum tipleri için 0 özel bir değere sahiptir. Bu değer bir enum tipine atandığında otomatik olarak bu int değeri bir enum tipine çevrilir. Örneğin;


Renk r = 0;
Console.WriteLine(r);

Şeklinde bir kod yazdığımızda bu kodun çıktısı “Kırmızı” olur. Bu atama işlemini Hiza enum’u ile denemiş olsaydık bu enum’da bulunan Orta değeri 0 değerine atandığı için bu sefer ilk elemanı değil Orta değerini ekrana yazdırmış olacaktır. Fakat bu atama ve çevrilme işlemleri sadece 0 değeri için geçerlidir. Bu yüzden aşağıdaki kodumuz “Cannot convert type int to Renk” şeklinde bir hata verecektir.


Renk r = 1;
Console.WriteLine(r);

0’a enumlar tarafından bu şekilde davranmalarının iki nedeni vardır:

  • Bir enum’un ilk üyesi sıklıkla enum’un default value’su olarak kullanılır.
  • Birleşik enum tipleri için 0’ın anlamı “Flag Yok”

Ayrıca bu çevirme işlemlerini boolean tipler için de kullanabiliriz:


int i = (int)Hiza.Sol;
Hiza h = (Hiza) i;
bool Solmu = (int) h == -1;
Console.WriteLine(Solmu);

Burada da öncelikle Hiza enumunda yer alan Sol üyesinin üye sıralamasındaki yeri alınır. (0 numaralı üye) Daha sonra bu sıralama değeri Hiza tipinde hangi değişkene denk geldiği belirlenir. (0. üye Sol adlı üye) Ardında da bu üyenin değerinin –1 olup olmadığı (int)h ile belirlenip bool değişkenine atanır.

Enum’ları başka bir enum tipine açıkça atayabiliriz. Örneğin; YatayHiza adında bir enum oluşturalım:


enum YatayHiza
 {
Sol = Hiza.Sol,
Sağ = Hiza.Sağ,
Orta
 }

Bu tanımlamadan sonra aşağıdaki şekilde tanımlamalar yapabiliriz:


YatayHiza y = (YatayHiza)Hiza.Sağ;
YatayHiza i = (YatayHiza)(int)Hiza.Sağ;

Burada y ve i değişkenlerini yazdırdığımızda ikisinde de “Sağ” sonucunu elde ederiz.

Flag Kullanımı

Enum üyelerini birleştirebiliriz. Karışıklığı önlemek için, enum üyelerini değerleri ile birleştiririz. Örneğin aşağıdaki enum tanımımızı yapalım:


YatayHiza y = (YatayHiza)Hiza.Sağ;
YatayHiza i = (YatayHiza)(int)Hiza.Sağ;

Enum üyelerini birleştirmek için | ve & gibi bir operatörlerini kullanırız. Bu yüzden bu üyelerin değerlerini 2’nin üsleri şeklinde tanımladık:


[Flags]
 enum Kenar
 {
Sol = 1,
Sağ = 2,
Üst = 4,
Alt = 8
 }

| operatörü ile SağSol enum değişkenini oluşturalım. SağSol ve Sol üyelerini & operatörü ile and’lersek ortak payda Sol olduğundan altındaki ifadeyi yazdırır. formatlı değişkeni “Sol, Sağ” olarak tanımlanır Kenar tipindeki enum’u string’e çevirdiğimizde. k değişkenini de farklı yolla SağSol değişkenine eşitledik. Son olarak ta k değişkenini Sağ değişkeni ile ^ (XOR) işlemine soktuğumuzda geriye sadece Sol kalır.

Kural ile, Flags niteliği bir enum’un üyeleri birleştiriliyorsa her zaman uygulanmalıdır o enum yapısına. Eğer Flags niteliği kullanmadan bir enum oluşturur ve yine bu üyeleri birleştirmek isterseniz sorun çıkmaz. Yani yukarıdaki enum tanımlamasında [Flags] kısmını silsek bile bu programımız sorunsuz çalışır. Fakat, bir enum örneği için ToString() metodunu çağırmak bir string yerine bize bir numara getirecektir.

Kural ile, birleştirilmiş enum tipi tekil değil de çoğul tipte isimlerden oluşmalıdır.

Ayrıca enum üyelerini enum içerisinde tanımlama aşamasında da birleştirebiliriz:


[Flags]
 enum Kenar
 {
 Sol = 1,
 Sağ = 2,
 Üst = 4,
 Alt = 8,
 SolSağ = Sol | Sağ,
 AltÜst = Alt | Üst,
 Tüm = SolSağ | AltÜst
 }

Enumlarla ilgili işlem yapabileceğimiz operatörler aşağıdaki şekildedir;

= == != < > <= >= + – ^ & | ˜
+= -= ++ – sizeof

Bit bir, aritmetik ve karşılaştırma operatörleri geriye underlying değerlerinin işlemler sonuçlarını geri döndürür.

Type-Safe Konusu

Aşağıdaki enum’ yapısını göz önünde bulunduralım;


enum Kenar
 {
Sol,
Sağ,
Üst,
Alt
 }

Bir enum kendi underlying type versine atanabildiği gibi dışarıdan gerçek bir değer de enum’a bound edilebilir. Örneğin;


Kenar k = (Kenar)12345;
Console.WriteLine(k);

kodu bize 12345 çıktısını verir. Fakat bit ve aritmetik operatörler benzer bir şekilde geçersiz bir değer üretirler. Bu yüzden;


Kenar k = Kenar.Sağ;

k++;

kodu herhangi bir hata oluşturmaz.

Bir enum değerinin geçerli olup olmadığı kontrol etmek için Enum.IsDefined statik metodunu kullanabiliriz. Örneğin;


Kenar kenar = (Kenar)1;
 Console.WriteLine(Enum.IsDefined(typeof(Kenar), kenar));
 Kenar kenar1 = (Kenar)12345;
 Console.WriteLine(Enum.IsDefined(typeof(Kenar), kenar1));
 Kenar kenar2 = Kenar.Sol;
 Console.WriteLine(Enum.IsDefined(typeof(Kenar), kenar2));

kodumuzun çıktısı sırayla “True, False, True” olur. İlk satırımızın neden True döndürdüğünü yine bu konu içerisinde anlatmıştık.

Ne yazık ki, Enum.IsDefined metodu flag kullanılmış enum yapılarında kullanılamaz. Fakat aşağıdaki gibi tanımladığımız FlagTanımlandımı adında bir metod ile bir enum yapısının flag kullanılıp kullanılmadığını kontrol edebiliriz:


[Flags]
 enum Kenar
 {
 Sol = 1,
 Sağ = 2,
 Üst = 4,
 Alt = 8
 }

class Test
 {
 static bool FlagTanımlandımı(Enum e)
 {
 decimal d;
 return !decimal.TryParse(e.ToString(), out d);
 }

static void Main()
 {
 for (int i = 0; i <= 16; i++)
 {
 Kenar kenar = (Kenar)i;
 Console.WriteLine(FlagTanımlandımı(kenar) + " " + kenar);
 }
 }
 }

C# Delegate Tipine Genel Bir Bakış

Bir delegate tipi, özel parametre listesi ve geri dönüş tipi ile birlikte bir metoda referans bildirir. Böylece bir delegate tipi oluşturursak bir metoda referans içeren bir nesne yaratmış oluyoruz. Bu metod ise bu referans ile çağırılabilir konuma gelmiş oluyor.

Aşağıdaki örneğimiz bir Fonksiyon adında bir delegate tanımlar ve onu kullanır;

using System;

namespace ConsoleApplication1
{
 delegate double Fonksiyon (double d);

class Carpma
 {
 double carpim;

public Carpma(double carpim)
 {
 this.carpim = carpim;
 }

public double Carp(double x)
 {
 return x * carpim;
 }
 }

class Test
 {
 static double Kare(double x)
 {
 return x * x;
 }

static double[] Uygula(double[] a, Fonksiyon f)
 {
 double[] sonuc = new double[a.Length];

for (int i = 0; i < a.Length; i++)
 sonuc[i] = f(a[i]);

return sonuc;
 }

static void Main()
 {
 double[] a = { 0.0, 0.5, 1.0 };

double[] kareler = Uygula(a, Kare);

double[] sinusler = Uygula(a, Math.Sin);

Carpma c = new Carpma(2.0);
 double[] ciftler = Uygula(a, c.Carp);
 }
 }

}

Fonksiyon delegate tipinin örneği double tipinde argüman alan ve geriye double türünde bir veri döndüren herhangi bir metoda referans olabilir. Uygula() metodu verilen Fonksiyon’u double[] ile uygular ve geriye bir double[] sonucunu döndürür. Main metodu içerisinde, Uygula 3 farklı fonksiyonu double[] ile uygular.

Bir delegate, bir statik metoda (Kare ve Math.Sin metodları gibi) ya da bir metod örneğine (c.Carp metodu gibi)referans olabilir.

Bir metod örneğine referans olan bir delegate, belirli bir nesne örneğine de referans olabilir. Ve bu metod örneği delegate ile çağırıldığında, bu nesne this ile birlikte yönelir.

Bir delegate örneği yaratmak için, bir metodu delegate değişkenine atayabiliriz:

using System;

namespace ConsoleApplication1
{
 delegate int Donusturme (int x);

class Test
 {
 static int Kare (int x)
 {
 return x * x;
 }

static void Main()
 {
 Donusturme d = Kare; //Delegate örneği oluşturma
 int sonuc = d(3); //Delegate'i çağır
 Console.WriteLine(sonuc); //9
 }
 }

}

Görüldüğü üzere, bir delegate örneği çağırmak, tıpkı bir metod çağırımına benzer. (d(3) gibi) Bu arada;

 Donusturme d = Kare; 

satırı aşağıdaki kodun kısaltılmış halidir;

Donusturme d = new Donusturme (Kare);</p>  <p>d(3)

kodumuz da

d.Invoke(3)

kodunun kısaltılmışıdır.

Delegate Uyumlu Metodlar Yazmak

Bir delegate değişkeni metodlara dimanik olarak atanır. Bu delegate uyumlu metod yazmanın anahtarıdır. Aşağıdaki örnekte, Donustur adından bir yardımcı metodumuz var ve bir dizinin elemanlarını karelerine dönüştürür:

using System;

namespace ConsoleApplication1
{
 delegate int Donusturme (int x);

class Yardımcı
 {
 public static void Donustur(int[] degerler, Donusturme d)
 {
 for (int i = 0; i < degerler.Length; i++)
 degerler[i] = d(degerler[i]);
 }
 }

 class Test
 {
 static int Kare (int x)
 {
 return x * x;
 }

static void Main()
 {
 int[] degerler = { 1, 2, 3 };
 Yardımcı.Donustur(degerler, Kare);

foreach (int i in degerler)
 Console.Write(i + " "); // 1 4 9
 }
 }
}

Her delegate örneği çoklu kullanım yeteneğine sahiptir. Bunun anlamı; bir delegate örneği sadece tek bir metoda referans değil, birden fazla metoda referans olabilir. + ve += operatörleri delegate örneklerini birleştirir. Örneğin:

 DelegateOrnek d = MetodOrnek;</p>  <p>d += FarklıMetodOrnek; 

Bu durumda d delegate’ini çağırmak bu iki metodu çağırmak anlamına gelir. Eklenilme sırasına göre delegate tipleri çağırılır.

– ve –= operatörleri de sağıdaki metodu solundaki delegate örneğinden silerler. Örneğin;

 d –= MetodOrnek; 

Bu durumda d delegate’ini çağırmak sadece FarklıMetodOrnek metodunu çağırmamız anlamına gelir.

NOT: Delegate tipleri immutable tiplerdir. Bunun anlamı += veya –= operatörlerini kullandığınızda yeni bir delegate örneği oluşturmuş olursunuz ve varolan değişkeni ona atarsınız.

Çoklu gönderilen bir delegate tipi eğer void olmayan bir geri dönüş tipine sahipse, çağıran bu delegate’in içerdiği son metodun geri dönüş değerini alır. Önde bulunan metodlar yine de çağırılır fakat bunların geri dönüş değerleri görmezden gelinir.

NOT: Tüm delegate tipleri dolaylı olarak System.MulticastDelegate sınıfından türetilir. O da System.Delegate sınıfından kalıtım alır. += ve –= operatörlerini kullandığımızda aslında System.Delegate sınıfının statik tanımlanmış Combine ve Remove metodlarını kullanmış oluruz.

Statik Metod vs Metod Örneği

Bir delegate nesnesi bir metod örneğine atandığında, sadece atandığı metod olan referansı değil, ayrıca ait olduğu metodu da korur. System.Delegate sınıfının Target özelliği bu örneği gösterir. Örneğin;

using System;

namespace ConsoleApplication1
{
 public delegate void İlerleme (int TamamlanmaYuzdesi);

class Test
 {
 static void Main()
 {
 Ornek o = new Ornek();
 İlerleme i = o.İlerlemeOrnek;
 i(90); // 90
 Console.WriteLine(i.Target == o); // True
 Console.WriteLine(i.Method); // Void İlerlemeOrnek(Int32)
 }
 }

class Ornek
 {
 public void İlerlemeOrnek(int TamamlanmaYuzdesi)
 {
 Console.WriteLine(TamamlanmaYuzdesi);
 }
 }
}

Generic Delegate Tipleri

Bir delegate tipi generic tipinde parametreler içerebilir. Örneğin;

public delegate T Donusturme<T> (T arg);

Bu tanımlama ile, Donustur yardımcı metodunu herhangi bir tip ile kullanabiliriz:

 public class Yardımcı
 {
 public static void Donustur<T> (T[] degerler, Donusturme<T> t)
 {
 for (int i = 0; i < degerler.Length; i++)
 degerler[i] = t(degerler[i]);
 }
 } 

Func ve Action Delegate’leri

Generic delegate’ler ile, herhangi bir geri dönüş tipi ve herhangi sayıda argümanı olan metodlar için küçük delegate tipleri oluşturmak mümkündür. Bu delegate tiplerinden olan Func ve Action delegate’leri System namespace’i içerisinde yer alır.

 delegate Sonuc func<out Sonuc>();
 delegate Sonuc func<in T, out Sonuc>(T arg);

delegate void Action ();
 delegate void Action<in T>(T arg); 

Bir önceki örneğimizde Donustur delegate örneğini, sadece basit bir T argümanı alan ve aynı cinsten bir değer geri döndüren Func delegate’i ile yer değiştirebiliriz:

 public static void Donustur<T> (T[] degerler, Func <T, T> donus)
{
 for (int i = 0; i < degerler.Length; i++)
 degerler[i] = donus (degerler[i]);
} 

Delegate  Uyumluluğu

Tip

Tüm delegate tipleri, imzaları aynı olsa bile birbiri ile uyuşmayan tiplerdir.

 delegate void D1();
 delegate void D2();

D1 d1 = Metod1;
 D2 d2 = d1; // Derleme zamanı hatası 

Aşağıdaki örnek ise herhangi bir hata vermez:

 D2 d2 = new D2 (d1); 

Delegate örnekleri eğer aynı metodlara hedef gösterilirse bunlar eşit olarak nitelendirilir. Örneğin:

 delegate void D();
 ...
 D d1 = Metod1;
 D d2 = Metod1;
 Console.WriteLine (d1 == d2); // True döndürür. 

Birden fazla metoda referans gösteren delegate tipleri, eğer aynı metodlar aynı sırayla ise bu tipler de eşit olarak nitelendirir.

Parametre

Bir delegate tipi, hedef gösterdiği metodtan daha belirli parametre tiplerine sahip olabilir. Bu duruma contravariance adı verilir. Örneğin;

 using System;

namespace ConsoleApplication1
{
 delegate void StringEtki (string s);

class Test
 {
 static void Main()
 {
 StringEtki se = new StringEtki(NesneEtki);
 se("Merhaba");
 }
 static void NesneEtki(object o)
 {
 Console.WriteLine(o);
 }
 }
} 

Bu örneğimizde, StringEtki delegate tipi string tipindeki bir argüman ile çağırılır. Ve bu argüman hedef metoda aktarıldığında, bu argüman dolaylı olarak object tipine çevrilir (üst tipi olduğundan).

Geri Dönüş

Delegate’in geri dönüş tipi hedeflediği metodun geri dönüş tipinden daha az belirli olabilir. Bu duruma da covariance adı verilir. Örneğin;

 using System;

namespace ConsoleApplication1
{
 delegate object NesneGetir ();

class Test
 {
 static string StringGetir()
 {
 return "Merhaba";
 }
 static void Main()
 {
 NesneGetir o = new NesneGetir(StringGetir);
 object sonuc = o();
 Console.WriteLine(sonuc); //Merhaba
 }
 }
} 

NesneGetir geriye bir object döndürmesi bekler, fakat object alt sınıfı bunu ayrıca yapar. Delegate’in geri dönüş tipi “covariant”’tir.

C#’ta Dizi Yapısı

C#’ta bir dizi, doğrudan ulaşılabilen sayısı belli üyeleri içeren bir veri yapısıdır. Dizinin içindeki elemanlar, dizi elemanları, hepsi aynı tipte ve bu tip dizi elemanlarının tipi olarak adlandırılır.

Dizi tipleri birer referans tipidir ve dizi elemanlarının tanımlanması bu dizi örneğine referans içerir. Gerçek dizi örneği dinamik olarak runtime zamanında new operatörü kullanılarak elde edilir. New operatörü kullanılarak dinamik olarak dizilere bellek ayrılır. New operatörü otomatik olarak dizi elamanlarının default değerlerini ilk kullanıma hazırlar. Örneğin; tüm sayısal tipler için 0, tüm referans tipler için ise null.

Örneğin aşağıdaki örnek, bir int dizisi yaratıp, ilk kullanıma hazırlayıp, sonra da çıktılarını gösteren bir örnektir;

using System;

class Test 
{ 
    static void Main() 
    { 
        int[] dizi = new int[10]; 
        for (int i = 0; i < dizi.Length; i++ ) 
        { 
            dizi[i] = i * i; 
        } 
        for (int j = 0; j < dizi.Length; j++ ) 
        { 
            Console.WriteLine("dizi[{0}] = {1}", j, dizi[j]); 
        } 
    } 
}

Buradaki örnekte tek bir boyutlu dizi ile çalıştık. C# birden fazla boyutlu dizi kullanımına izin vermektedir. Bir dizinin boyut numarası aynı zamanda o dizi tipinin rank’ı olarak ta bilinir. Bir dizinin boyut sayısını dizi tanımında kullanılan dizinin içindeki virgül sayısını bir arttırarak bulabiliriz. Aşağıdaki örnekler bir, iki ve üç boyutlu dizilere örneklerdir;

int[] dizi1 = new int[10]; 
int[,] dizi2 = new int[10, 5]; 
int[, ,] dizi3 = new int[10, 5, 2];

dizi1 dizisi 10 elemana, dizi2 dizisi 50 (10 * 5) elemana ve dizi3 dizisi de 100 (10 * 5 * 2) elemana sahiptirler.

Bir dizinin eleman tipi herhangi bir tip olabilir hatta dizi tipinde bile olabilir. Dizi tipindeki elemanlara sahip dizilere Jagged array adı verilir çünkü bu dizinin elemanlarının uzunlukları aynı olmak zorunda değildir. Aşağıdaki örnek int dizi tipi içeren elementlere sahip olan bir diziyi ifade eder.

int[ ][ ] dizi = new int[3][ ]; 
dizi[0] = new int[10]; 
dizi[1] = new int[7]; 
dizi[2] = new int[20];

array-1

Yukarıdaki ilk satır, 3 elemanlı bir dizi oluşturur, bu elemanların hepsi int[] tipinde ve hepsinin varsayılan değeri null’dır. Altındaki satırlar, bu int[] tipindeki dizi elemanlarının uzunluklarını belirlerler.

Unutmayın ki new operatörü { } sınır belirleyiciler içinde dizinin elemanlarına ilk oluşturulma anında değer atamaya imkan verir.

int[ ] dizi = new int[ ] { 1, 2, 3 };

Hatta bu kullanımı aşağıdaki şekilde kısaltabiliriz de.

int[] dizi = { 1, 2, 3 };

Bu iki kullanım da aşağıdaki gösterilen adımlara eşittir:

int[ ] t = new int[3]; 
t[0] = 1; 
t[1] = 2; 
t[2] = 3; 
int[ ] dizi = t;

Dizilerde referans atama konusunda tıpkı nesneler gibi, bir diziyi diğer diziye atadığınızda sadece değişkenin ilişkili olduğu nesneyi değiştiriyorsunuz. Örneğin aşağıdaki programda, dizi tanımlamaları yapılan dizi1 ve dizi2 dizileri, atama yapıldıktan sonra her iki dizi referans değişkenleri de aynı nesneyi gösterdiğinden dizi değerleri aynı olur.

using System;

class Test 
{ 
    static void Main() 
    { 
        int[] dizi1 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 
        int[] dizi2 = { 0, -1, -2, -3, -4, -5, -6, -7, -8, -9 };

        dizi2 = dizi1;

        foreach(var i in dizi1) 
        { 
            Console.Write(dizi1[i]); 
        }

        Console.WriteLine();

        foreach (var i in dizi2) 
        { 
            Console.Write(dizi2[i]); 
        } 
    } 
}

Çıktı:

0123456789

0123456789 Press any key to continue . . .