F# bir .NET ailesi dili. Bu yüzden, temel veri tipleri ve referans’ları C# ile çok benziyor.

F# bir strongly typed dildir. Bu nedenle tür dönüşümlerinde oluşan bir hata compile time’da meydana gelir. Bu sayede bu tür hatalar, yazılım geliştirmenin erken evrelerinde kontrol edilip müdahele edilebilir.

C# ile F# arasındaki farklardan biri de; F# örnekleri açıkça tip belirtilmesine ihtiyaç duymaz, bu yüzden genellikle atanan değerden tipi anlayabilir. C# geliştiricileri için bu durum var keyword’ünü anımsatır. Fakat let ile var anahtar sözcükleri arasında bazı temel farklılıklar da mevcuttur.

Aşağıdaki tablo Literals (F#) sayfasının bir kopyası gibi olacak ama yine de C# ve F# gösterimini göstermek istedim;

Veri Tipi C# Gösterimi F# Gösterimi
string string s = “abc” let s = “abc”
char char c = ‘c’ let c = ‘c’
int int i = 5 let i = 5 veya
let i = 5l
Uint uint i = 5U let i = 5u veya
let i = 5ul
decimal decimal d = 5m let d = 5m veya
let d = 5M
short short s = 5 let s = 5s
long long l = 5L let l = 5L
ushort ushort u = 5 let u = 5us
ulong ulong u = 5UL let u = 5UL
byte byte b = 64 let b = 64y
let b = ‘b’B
sbyte sbyte sb = 64 let sb = 64uy
boolean bool b = true let b = true
double double d = 0.3
double d = 0.3d
double d = 3e-1
let d = 3.0 veya
let d = 3e-1 veya
let d = 3.
float float f = 0.3f
float f = 0.3F
let f = 0.3F

 

İlginç bir F# özelliği de, “B” suffix’i ile bir ASCII string’i gösteren byte dizisi oluşturabiliyorsunuz. Direkt olarak Encoding.ASCII.GetBytes fonksiyonunu çağırmak yerine bu suffix kullanılabilir. .NET Framework içerisinde string yapısı Unicode tabanlıdır. Bu yüzden bu yapı biraz garip gelebilir size. Aşağıdaki örnekte, AsciiString bir byte[] tipindedir.

let asciiString = "xyz"B // F# gösterimi
 
byte[] asciiBytes = Encoding.ASCII.GetBytes("xyz"); // C# gösterimi

C#’ın aksine, float tipi F# içerisinde 64 bit’tir (double precision floating-point) ki C#’ta double tipine eşdeğerdir. C# içerisinde float 32 bit (single precision floating-point) memory kaplar ki bu da F#’ta float32 tipine eşdeğerdir.

F# derleyicisi “implicit type conversion”’a izin vermez. Bir C# geliştiricisi için int tipi bir değer float tipinde bir değere herhangi bir işlem yapmadan çevirebilir. Burada bu değer 13’ten 13.0’a dönüşür. Bu F# içerisinde yasak olduğundan integer 13’ü bir float 13’e dönüştürmek için “explicit conversion” yapmanız gerekir.

String tipine bakacak olursa F# 2.0 versiyonunda aynı şu andaki C# string literal’de olduğu gibi iki farklı string var görünüyor; normal string ve verbatim string. Bir de F# 3.0 versiyonu ile triple-quoted string eklenmiş.

let a = "Son karakter bir tab \t"
let b = @"Son karakter bir tab \t"

Bunların F# interactive penceresinde çıktıları şu şekilde olur;

val a : string = "Son karakter bir tab    "
val b : string = “Son karakter bir tab\t"

Bunun dışında C#’tan bildiğiniz gibi escape sequence karakterleri (örneğin; ve \) göstermek isterseniz onları escape etmeniz gerekir. Örneğin;

let a = "Bu gecerli bir \"string\""
let b = @"Bu gecerli bir ""string"""

F# 3.0 ile gelen triple-quoted string bu acıyı hafifletmiş gibi. 3 tane çift tırnak’ın (”””) arasındaki herşey verbatim olarak algılanıyor. Bu yüzden çift tırnak ya da backslash gibi karakterleri escape etmenize gerek yok. Burada da şöyle bir kural var; string tek bir çift tırnak ile başlayabilir ama tek bir çift tırnak ile bitemez. Ayrıca XML tarafında da faydalı olduğu söyleniyor bu üçlü çift tırnak yapısının ama o konuda pek bilgim yok.

let a = """Bu gecerli bir "string" """
let b = """" Bu gecerli bir string """
let c = """ Bu gecersiz bir string """" // Geçersiz string

F# değişken isimlendirmede de bazı kolaylıklar sağlıyor. Bildiğiniz gibi okunabilir bir değişken ismi her zaman yazılımcılar için iyi bir pratiktir. F#’ta alfabetik olmayan değişken isimleri yazmak için çift ters tırnak kullanabilirsiniz.

//Boşluk içeren değişken ismi
let ``benim değişkenim`` = 4
 
//Keyword ile değişken ismi
let ``if`` = 5
 
//Tek tırnaklı değişken ismi
let a' = 6
 
// # içeren değişken ismi
let ``F#`` = "F#"

Akış Kontrolleri

F#’ta imperative stili programlar yazmanız için akış kontrollerini bilmeniz gerekir. For loop, while loop ve if expression bunlardan bazıları. C#’ta { ve } scope ayrımları için kullanırken F#’ta boşluk kullanılır. Visual Studio otomatik olarak tab’ı boşluk’a çevirir.

F#’ta iki çeşit for döngüsü bulunuyor: for…to/downto ve for…in.

for…to/downto döngüsü bir başlangıç değerinden bir bitiş değerine tekrarlanır. C#’taki for döngüsüne çok benzerdir.

for…in döngüsü ise bir pattern ile eşleşen enumerable collection içinde tekrarlanır. Örneğin; Range expression, List, diziler.. C#’taki foreach döngüsüne çok benzer. C#’ta 1’den 100’e kadar olan sayılar üzerinde şu şekilde dönebilirsiniz;

for (int i = 0; i < 100; i++)
{
}

F#’ta şu şekillerde gösterebiliriz;

for i = 0 to 100 do ...
 
for i in [0...100] do ...

Veya 100’den geriye olarak;

for i = 100 downto 0 do ...

Peki for…to/downto döngüsü ile ikişer ikişer nasıl ilerleyebiliriz? Bildiğim kadarıyla bu döngü bunu desteklemiyor. Mecburen for…in ile bir sequence veya list kullanmamız gerekiyor.

F#’ta while loop C#’taki ile aynı şekilde işlev görür. C#’ta şu şekilde;

int i = 0;
while (i <= 10)
{
    i++;
}

F#’ta ise şu şekilde gösterilir;

let mutable i = 0
while i <= 10 do    
i <- i + 1

F#’ta statement’lar sonunda noktalı virgül (;) kullanımı zorunlu değildir. Fakat bir satırda birden fazla statement yazmanız gerektiği bir durumda noktalı virgül kullanmak zorunda kalırsınız.

Gördüğünüz üzere yukarıdaki F# örneğinde mutable anahtar sözcüğünü kullandık. Bunun anlami i bir mutable değişkendir ve <- operatörü ile değeri değiştirebilir. Bu bizi F#’ta farklı bir kavrama götürür. Bunun anlamı; F#’ta mutable keyword’ü ile tanımlanmayan bir değişken immutable object’tir. Onun değerini değiştiremezsiniz. Bu yüzden C#’ta int i = 0 statement’ı F#’ta let mutable i = 0 statement’ına eşdeğerdir. Bu küçük bir değişiklik olarak görünse de, aslında çok temel bir değişikliktir. C#’ta değişkenler varsayılan olarak mutable iken F# değişkenler default olarak immutable’dır. Bu kavramı F# ile Fonksiyonel Programlama yazısında anlatmıştım.

Yukarıda söylediğim gibi F#’ta if bir expression’dır. Bunun anlamı geriye bir değer döndürür. Her if/else kolu aynı tipte değer döndürmelidir. Eğer if kolu herhangi bir değer döndürmüyorsa else kısmı opsiyoneldir. Eğer if bir değer döndürüyorsa else kullanılmak zorundadır. Bu yapısıyla C#’taki ?: operatorüne benzer. If yapısı içerisinde başka bir koşul yerleştirmek için C#’taki if…else if yapısına istinaden F#’’ elif kullanır.

if a<b then "küçük"
elif a>b then "büyük"
else "eşit"

C# eşleşme statement kullanımında switch yapısını kullanırken F# match expression’ını kullanır.

C# gösterimi;

int i = 1;
switch (i)
{
	case 1 :
		Console.WriteLine (1);
		break;
	case 2 :
		Console.WriteLine (2);
		break;
	default:
		Console.WriteLine ("Sayı değil");
		break;
}

F# gösterimi;

let mutable i = 1;
match i with    
	| 1 -> printfn "1"    
	| 2 -> printfn "2"    
	| _ -> printfn "Sayı değil"

C# içerisinde kullandığımız Console.WriteLine() ile konsola yazdırdığımız çıktıları F# ile de kullanabiliriz. Kendisi bir .NET dili olduğu için sorun olmayacaktır. Ama F# bize daha güçlü bir yapı olan printfn yapısını sunuyor. Örneğin;

C#

Console.WriteLine("Toplam: {0}", toplam);

F#

printfn "Toplam: %A" toplam

F# dilinde printfn, Console.WriteLine() methoduna göre daha katıdır. C#’ta {numara} her türlü değişkeni alır ve bu değişkenin tipi hakkında endişelenmeniz gerekmez. F# spesifik bir tipteki değişken için spesifik formatlara ihtiyaç duyar. Bu şekilde F# bununla ilgili hata oluşma olasılığını azaltır. Tüm formatları şu sayfada görebilirsiniz.

Posted in F#.

Üniversiteye ilk başladığım yıllarda – ki programlama ile tanışmam bu senelere denk geliyor – programlamanın kolay olduğunu düşünüyordum. Neden zor olduğunu düşünmeliydim ki? Bir insanın ürettiği makinaya input’ları gönderip, makinın bu inputları işlemesi ve insanların anlayabileceği output’lar üretmesi bana hep kolay görünüyordu.

 

 

 

 

 

 

 

 

 

Programcı’nın Wikipedia’daki tanımına bakarsak şöyle yazıyor;

A programmer, computer programmer, developer, coder, or software engineer is a person who writes computer software. The term computer programmer can refer to a specialist in one area of computer programming or to a generalist who writes code for many kinds of software.

Bu yüzden bence programlamayı zorlaştıran nedenlerin başında Software development process’in bir derya deniz olması geliyor;

  • Harika bir input iletme
  • Harika bir output oluşturma
  • Input’ların açık ve sade bir şekilde dokümantasyonu
  • Programın dokümantasyonu
  • Programın test edilmesi
  • Problemin iyi belirlenmesi
  • Problemin çözümünün iyi belirlenmesi
  • Ve dahası..

Bu maddelerin hepsi daha derinden incelendiğinde programlama’nın temelinde şunun olduğuna inanıyorum;

 

Programlama problem çözmektir!

Aslında bir program, bilgisayara bir problemi nasıl çözeceğini anlatır. Zira üzerinde yaşadığımız dünya problemlerle dolu ve bilgisayar biliminde bile çözülemeyen birçok problem var hala.

Fakat bilgisayardan büyük bir problemi çözmesini istediğinizde, genellikle ona bu problemi oluşturan birden fazla küçük problemlerin çözümünü anlatmak zorundasınız. Daha basit problemleri çözdüğünüzde, bilgisayara nasıl çalışması gerektiğini anlatan programları yazmak her zaman daha kolay olacaktır.

Bir macar matematikçi olan George Pólya, How to Solve It: A New Aspect of Mathematical Method isimli kitabında problem çözmeyi şöyle özetlemiş;

  1. Problemi anla.
    • Problemi kendi sözcüklerinizle tanımayabilir misin?
    • Problemi daha iyi anlamak için bir resim veya diagram çizebilir misin?
    • Çözümü bulmak için yeterli bilgi mevcut mu?
    • Problemin içerisindeki tüm kelimeleri anladınız mı?
    • Cevap almak için soru sormaya ihtiyacınız var mı?
  2. Bir plan yap.
    • Tahmin et.
    • Sıralı bir liste yap.
    • Olasılıkları ele.
    • Özel durumları değerlendir.
    • Denklemleri çöz.
  3. Planı uygula.
  4. Çalışmanı gözden geçir.

Peki ya bir problem için plan yapmak problemin kendisiyse? O probleme nasıl hamle yapacaksınız?

Pólya bu konuda şunu belirtmiş;

If you can’t solve a problem then there is an easier problem you can solve. Find it.

Peki bu ne işe yarar?

  • Daha basit problemlerin çözümü sizi daha zor problemlerin çözümüne teşvik edebilir.
  • Basit problemlerin çözümü özgüveni arttırmak için iyi bir pratiktir. Böylece basit problemlerin çözümünü içeren bir kod ile çalışmaya başlamanız size başarıya ulaştırmada yardımcı olacaktır.
  • Zor problemleri çözmede başarısız olsanız bile daha basit problemleri çözmek size kredi kazandırır.

Hemen bir örnek verelim. Bilgisayarda konsol üzerinden ardı ardına celsius ölçeğinde girilen 10 sıcaklığın Kelvin cinsinde karşılıklarını bulan bir program yazalım. Bu problemin çözümünde şu şekilde bir yol izleyebiliriz mesela;

  • Bilgisayarda konsol üzerinden girilen sadece bir değerin Kelvin değerindeki karşılığını bulmayı öğrenin.
  • Belki de bu çok zordur. O zaman bilgisayarda girilen değerlere matematiksel işlemleri uygulamayı öğrenin.
  • Belki de bu çok zordur. O zaman bilgisyara konsol üzerinden girilen sayısal değerin saklamnasını öğrenin.
  • Belki bu da çok zordur. O zaman bilgisayara konsol üzerinden sayısal değer girilmesini öğrenin.
  • Belki bu da çok zordur. O zaman bir konsol uygulaması oluşturmayı öğrenin.
  • Belki bu da çok zordur. O zaman kullandığınız dil ile Hello World yazmayı öğrenin.
  • Belki bu da çok zordur. O zaman kullandığınız IDE’yi açmayı öğrenin.

Kodlama problemlerinde de şu şekilde yollar vardır;

  • Eğer dikdörtgen çizmede bir sorununuz varsa, bunun yerine problemi çizgiler çizmeye indirgeyebilir misiniz? Peki ya noktalar oluşturmaya?
  • Eğer diskte bulunan bir text dosyasının içini okumada sorununuz varsa, bunun yerine problemi kendi yazdığınız string’leri okumaya indirgeyebilir misiniz?

 

Programlama için kötü koşullar

Eğer buraya kadar okuduysanız, programlamanın zorluğu hakkında bilgi sahibi olmuşsunuzdur. Neyseki, bu yüzden çalışma ortamları da programlamayı daha zor hale getirmek için tasarlanıyor. Muhtemelen birçoğumuz konsantrasyonumuzu bozacak gürültülere, konuşmalara, cep telefonu seslerine aşikar ortamlarda çalışıyoruz. Jeff Atwood – ki kendisi Stack Overflow kurucularından biridir – The Programmer’s Bill of Rights isimli yazısında bir programcıya sağlanması gereken tüm fiziksel ve çevresel gereksinimleri anlatıyor;

It’s unbelievable to me that a company would pay a developer $60-$100k in salary, yet cripple him or her with terrible working conditions and crusty hand-me-down hardware. This makes no business sense whatsoever. And yet I see it all the time. It’s shocking how many companies still don’t provide software developers with the essential things they need to succeed.

 

Programlama zor değil, sadece zaman alır

Hayatımda okuduğum en iyi yazılardan biri kesinlikle Peter Norvig’in yazdığı Teach Yourself Programming in Ten Years isimli makalesi.

Herhangi bir kitapçıya gittiğinizde Teach Yourself Java in 7 Days (7 Günde Java Öğrenin) benzeri, size birkaç günde veya birkaç saatte Visual Basic, Windows, Internet (vs.) öğretmeyi vadeden kitaplarla karşılaşırsınız.

Birkaç günde, Beethoven, Kuantum Fiziği ya da köpek eğitimi öğreten kitaplar yok. Bu sonuçlara bakılırsa, ya insanlar bilgisayar hakkında yeni şeyler öğrenmek için çok hevesli ve aceleci ya da bu iş bir şekilde inanılmaz derecede kolay.

İlk olarak 7 gün kayda değer programlar yazarak, yazdığınız programlardaki başarı ve başarısızlıklarınızdan ders almanıza yetecek bir süre değildir. Ne tecrübeli bir programcıyla çalışmaya, ne de o ortamın içinde yaşamanın nasıl bir şey olduğunu anlamaya zamanınız olmayacaktır. Bu durumda sadece yüzeysel bir aşinalıktan bahsedilebilir, derinlemesine bir kavramadan değil. Kısacası dili iyi öğrenmeye fırsatınız olmayacaktır. Alexander Pope’nin de söylediği gibi bir “yarı bilgili olmak çok tehlikelidir”.

Bu yazıyı ne zaman okusam, hemen aklına üniversiteden mezun olup 2-3 yıl bir şirkette tecrübe edindikten sonra “Senior Developer” olan arkadaşlarım gelir. Biraz üzücü buluyorum bu durumu doğruyu söylemek gerekirse zira onlar muhtemelen “yarı bilgili” konumundalar. Bir kısmının artık işyerinde verilen işleri yerine getirmekten başka uğraşı yoktur. Kitap okumazlar, blog yazmazlar, blog okumazlar, kod okumazlar, open source projelere katkıda bulunmazlar, başka programcılarla iletişim kurmazlar, programlama konusunda insanlara yardımcı olmazlar vs vs..

Fakat bu bir yarış değil. Kendinizi başka programcılarla karşılaştırmayın. Jeff Atwood Why I’m The Best Programmer In The World isimli yazısında şöyle diyor;

But it’s not our job to be better than anyone else; we just need to be better than we were a year ago.

fsharpF# öğrenmeye karar verdim.

Neden mi? Aslında bunun temel olarak 3 nedeni var;

  • Hayatımda hiçbir zaman fonksiyonel programlama temelinde bir dil öğrenmedim. Bunun nedeni de galiba OOP’nin günümüzde en popüler progrmalama paradigması olmasını gösterebilirim. Üniversite hayatım boyunca ve kısa süreli çalışma hayatım boyunca fonksiyonel programlamaya hiç ihtiyaç duymamam da buna neden oldu diyebilirim.
  • “Guru” olarak adlandırabileceğim birçok kişiden bu dili öğrenmenin ne kadar eğlenceli olduğunu, uygularken de bulmaca çözmek gibi bir his verdiğini duyunca açıkçası daha fazla dayanamadım. Adeta bir lezzetli çipetpet havası var F#’ta.
  • Yeni bir programlama dili öğrenmek ne zaman eğlenceli olmadı ki?

 

Kısaca Fonksiyonel Programlama

Fonksiyonel programlama, programları, argümanları ve dönüş değeri (return value) olan fonksiyonlar toplamı olarak görür. Nesneye yönelik programlamanın aksine, iteration işlemi için loop’lar kullanmak yerine çoğunlukla recursion kullanır. Program içerisindeki fonksiyonlar daha çok matematiksel fonksiyonlara benzerler zira programın durumunu değiştirmezler. Kısaca, bir değer bir identifier’a atandığında, asla değişmez. Fonksiyonlar parametre değerlerini değiştirmezler ve bu fonksiyonların sonuçları tamamen yeni bir değer döndürürler. Daha da kısaca, bir değer bir bellek bölgesine atandığında, asla değişmez. Fonksiyon sonuçları oluşturmak ise, önce fonksiyonlar değerleri kopyalar, bu kopyalanan değerleri değiştirerek işlem yaparlar. Orijinal değerleri başka fonksiyonlar kullanabilsin diye serbest bırakırlar ve artık onlara ihtiyaçları kalmadığında boşa çıkartırlar. Garbage Collection kavramının temelini oluşturur bu.

Bu sayede Fonksiyonel programlamanın birçok karmaşık hesaplama problemlerine çözümler sunduğu söylenir.

Fonksiyonel programlama ile ilgili birkaç kavramı da bilmek gerekiyor;

  • Higher order functions: Matematik ve genel bilgisayar bilim’inde bu fonksiyonlar en azından şu iki işlevden birini yapar;
    • Bir veya birden fazla fonksiyonu input alma veya
    • Output olarak bir fonksiyon verme. Diğer tüm fonksiyonlar first-order functions olarak isimlendirilir. Bu fonksiyonlara örnek olarak türev ve integral gösterilebilir. Zira ikisi de input olarak bir fonksiyon alıp geriye bir fonksiyon döndürürler. F# içerisinde bulunan List.map buna örnek olarak gösterilebilir.
  • First class functions: Bir elementin first class function olabilmesi için şunlardan biri olması gerekir;
    • Bir fonksiyona parametre olarak gönderilebilme
    • Fonksiyonlardan bir değer olarak dönebilme veya
    • Değişkenlere atanabilme. Programın herhangi bir yerinde bulunabilirler.
  • Pure functions: Bu fonksiyonlar şu iki maddeye birlikte sahiptirler;
    • Bu fonksiyonlar aynı input veya inputlarda aynı output’ları üretirler.
    • Bu fonksiyonların sonucu programda herhangi bir yan etkiye (side effect) neden olmazlar. (Mutable nesnelerin değişimi gibi veya I/O değişimi) Bu gibi fonksiyonlara örnek olarak Cos(x)  verilebilir.
  • Impure functions: Pure fonksiyonların tam tersi mantıkta çalışırlar. Örneğin;
    • Zamanı parametre alıp geriye haftanın günlerini döndüren bir fonksiyon. Zira farklı zamanlarda farklı değerler döndürebilir.
    • Random() fonksiyonu. Zira her çağırışımızda farklı bir değer döndürür. Ama Random(seed) fonksiyonu bir pure fonksiyondur. Zira bu fonksiyonu bu parametre ile her çağırışımızda aynı değer döndürür.
    • C’deki printf() fonksiyonu. Zira side effect olarak I/O’ya bir output verir.
  • Strict evaluation: Fonksiyonel programlama dilleri strict ve non-strict evaluation olarak kategorilendirilebilir. Temel konsept olarak fonksiyon argümanları nasıl işlenir ve expression’lar ne zaman hesaplanır kavramlarına dayanır bu kategorilendirme. Örneğin;
    • Strict evaluation altında
       print length([1+1, 2*2, 3/0, 4+4]) 

      kodu çalışmaz zira 3 / 0 division by zero döndürür. Fakat non-strict (lazy) evaluation altında bu geriye 4 sonucunu döndürür zira length içerisindeki expression’lar çalıştırılmaz. Sadece bunların uzunluğu bilgisi döndürülür. Strict evaluation altında bir fonksiyon çağırılmadan önce fonksiyonun tüm argümanları çalıştırılır.

Fonksiyonel programlamanın avantajlarını görmek için FP’nin yasakladıklarından çok, izin verdiği özelliklere bakılması gerektiği söylenilir. Örnek olarak fonksiyonel programlama ile fonksiyonlara bir değer gibi bakmak ve onları başka fonksiyonlara geçirmek. Bu başta pek önemli gözükmese de, etkileri olağanüstü olduğu söyleniyor. Data ve fonksiyonu birbirinden ayırmanın birçok problemin çözümüne olanak sağladığı söylenir. OOP programlamara göre FP programları daha kısa ve daha modülerdir.

Kısaca F#

F#’ın birçok sıkıntılı hesaplama problemlerini çözmek için en iyi yaklaşımlardan biri olduğu söyleniyor. F#, fonksiyonel programlama dışında imperative programming ve object-orianted yaklaşımlarına uyumluluk gösterir. Strongly typed özelliği mevcuttur, bu sayede programcıların belirsiz durumlar olmadığı sürece değişken tiplerini açıkça belirtmelerine gerek yoktur. C#’ta buna örnek olarak var keyword’ü gösterilebilir (tam karşılığı olmasa da). Bunun dışında inferred typing özelliğini de destekler.

F# bir “önce fonksiyon” (functional-first) dilidir. Buna rağmen az önce bahsettiğim gibi imperative programming ve object-orianted yaklaşımlarına da uyumluluk gösterdiğinden bu özellikler diğer Microsoft dillerine göre F#’ı daha esnek kılıyor.

F# temel olarak OCalm (object-orianted fonksiyonel programlama) dilinden modellendi. Ve .NET ile güçlendirildi. Generics kısmını hiçbir kod değiştirmeden destekler. Hatta IL (Intermediate Language) kodunu destekler. F# derleyicisi, sadece herhangi bir CLI (Common Language Infrastructure) için çalıştırılabilir dosyalar üretmez, ayrıca CLI olan her platformda çalışabilir. Bunun anlamı F# sadece Windows işletim sistemi ile sınırlı değildir. Ayrıca Linux ve Mac OS X üzerinde de çalışabilir.

Bu yazıdan yaklaşık bir ay önce OSS-approved license altında F# tamamen open source oldu. Microsoft tarafında, Visual Studio 2010, 2012 ve 2013 için tool’lar geliştiriliyor. Ayrıca F#’ın internet ortamında çok hareketli bir topluluğu mevcut. Birçok geniş ölçekli projelerde F# kullanarak katkıda bulunuyorlar. Birçok konferansta F# hakkında konuşmalar yapıyorlar. Twitter adreslerine şuradan ulaşabilirsiniz. Stack Overflow’da sorulara cevap veriyorlar. İnsanlara F# öğrenebilecekleri birçok eğitici site oluşturuyorlar.

F# Kurulumu

Yukarıda bahsettiğim gibi, F# birden fazla platformu destekler. Bu listeye buradan ulaşabilirsiniz.

Eğer windows üzerinde kullanıyorsanız, Visual Studio en iyi seçenek görünüyor. Visual Studio 2010 ve üzerinde F# default olarak kurulu olarak geliyor. Visual Studio 2008 kullanıyorsanız, MSDN üzerinden download edebilirsiniz. Eğer Vlsual Studio kullanmıyorsanız, Visual Studio Integrated Shell’i download edip, sonra F#’ı kurabilirsiniz. Temel olarak command-line compiler olan fsc.exe ve F# Interactive fsi.exe ihtiyaçlarınızı karşılayacaktır. Visual F# Resources sayfasından daha fazla bilgiye ulaşabilirsiniz.

Faydalı Linkler

Posted in F#.

Johs Susser gerçekten çok geyik bir programcı. Aynı zamanda iyi de bir girişimcidir. Geçenlerde gönderdiği tweet ile uzun zamandır üzerinde düşündüğüm bir konuya değindi kendisi.

Eğer sizde şimdiye kadar en az bir defa değişik zaman dilimlerinde çalışan bir yazılım projesinde görev aldıysanız, muhtemelen siz de Josh’un bu fikrine katılıyorsunuzdur. Projenizdeki dosyaların server’lara yüklenme zamanlarını ve bunları farklı zaman diliminde yaşayan insanlara göstermekte zorluklar yaşayabilirsiniz. Özellikle .NET programcıları için DateTime sınıfının bazı sorunları olduğundan bu durum daha karmaşık hale gelebilir. Şöyle belirginleştirelim bu durumu. Aşağıdaki kod parçasını ele alalım;

 Console.WriteLine(DateTime.Now == DateTime.UtcNow); 

Aslına bakarsanız başlarda bu kodun ne yapması gerektiğini bir türlü kavrayamamıştım. Fakat biraz daha üzerinde kafa yormaya başlayınca önümde 3 seçenek belirmeye başladı;

  • Her zaman True yazdırır, ikisi de aynı zamanı gösterir, sadece biri local zamanı diğeri de evrensel zamanı.
  • Her zaman False yazdırır, bu iki değer farklı çeşit veriyi ifade eder, bu yüzden bunlar otomatik olarak eşit olamazlar.
  • Eğer yerel zamanınız UTC ile senkronize ise, zaman dilimine uyulmaksızın True döndürür ki bu iki değer eşittir.

Bu kod benim derlediğim 4 farklı yerdeki derleyicide de False sonucunu döndürdü. Kendi bilgisayarımdaki derleyicide False sonucunu döndürmesi çok doğal tabi ki, zira İstanbul UTC+02.00 zaman diliminde olduğundan bu iki değer hiçbir zaman aynı zamanı göstermezler. Herneyse, DateTime hakkında bu kadar dedikodu yeter. Şimdi asıl konumuza dönelim.

Tayland..

Bildiğiniz gibi dünya üzerinde kullanılan birden fazla takvim var. Bu takvimlerden birçoğu etkili bir insanın doğum günü ile başlar. Standart uluslararası takvim Hz. İsa’nın doğum yılını 1 olarak kabul eder, ondan önceki her yıl milattan önce kabul edilir. Budist takvimi Buddha’nın ölümü ile başlar.

Fakat Tayland takviminde göre Buddha’nın 2556 yıl önce vefat ettiğini, yani Hz. İsa’dan 543 sene önce vefat ettiğini belirtir. Bu yüzden eğer Miladi takvim ile Tayland’ta hangi yıl olduğuna bakmak isterseniz miladı yıla 543 yıl eklemeniz gerekir.

 2013 + 543 = 2556 

Peki kod ile bu yıla ulaşmaya çalışsak? Çok basit. Yeni bir ThaiBuddhistCalendar() nesnesi oluşturup bunun GetYear() methodunu şu anki local zaman parametresi ile çalıştırabilirz ;


Console.WriteLine(new ThaiBuddhistCalendar().GetYear(DateTime.Now)); // 2556

Peki bunu bir de DateTime nesnesi oluşturup ulaşmaya çalışsak?


DateTime d = new DateTime(2013, 1, 1, new ThaiBuddhistCalendar());
Console.WriteLine(d.Year); // 1470

Görüldüğü üzere, burada Budist takvimine göre bir DateTime nesnesini oluşturmamıza rağmen, Year özelliği 1470 değerini gösterir. Çünkü DateTime nesnesi, hangi takvimi kullanacağını yorumlayamaz. Varsayılan olarak her zaman Miladi Takvimi kullanır. Bu yüzden Budist takvimine göre 2013 yılı için oluşturduğumuz DateTime nesnesinin Year özelliği Budist takviminde 2013 yılının Miladi takvimde 1470 yılını gösterdiği için bu değeri alır.

Eğer günlük zaman kaydetme işlemlerinde ya da zaman dilimleri hakkında en iyi çözümleri öğrenmek isterseniz aşağıdaki konuya bir göz atmanızı tavsiye ederim;

Peki neden Tayland?

Aslında ThaiBuddhistCalendar()’ı kullanan en temel ülke olmasının yanı sıra asıl neden başka..

The Hangover Part II

Peki ya başlık?

Başlığın ilham kaynağı da Türk sinema tarihinin gülen adamı Kemal Sunal’dan gelsin:

feyzo : bismillahirahmanirahim. hoca efendi anam sana gelecek.
haco efendi : ne ki ananın derdi ?
feyzo : der bende deva sende. bismillahhhh.. ” taaaak ! biloya tokat. ”
bilo : ne itip duriysen ula ?
feyzo : sus günah abdest alıyosun eşşolu eşşek. neuzübillah.. beni okutacak sana, kara sevda olmuşam çünkim.
haco : heç öyle bi hal görmiyem sende.
feyzo : bi yüzlük var sana helalinden. çaremde şu güloyla evlenmek. anamı razı et.
haco : çok az. iki yüzlük bide horoz, hemide iri.
feyzo : nerden bulam horozu ?! 150 gayme verem sulf olak.
haco : kurtarmaz. bide tavuk.
feyzo : anlaştık.

Bir önceki yazımızda sırasıyla geçiş yaptığımız argümanlara göre toplam maliyeti hesaplayan bir workflow oluşturmuştuk. Şimdiye kadar sadece taşıma ücretini hesaplamıştık. Şimdi bu yazımızda her bir ürünün lojik olarak maliyetlerini ekleyeceğiz.

Bu yüzden bir önceki yazımızda oluşturduğumuz Siparisİslemi projeminiz üzerinde çalışacağız. Visual Studio’yu başlatıp yeni bir proje oluşturalım. Other Project Types –> Visual Studio Solutions seçeneği ile Kopyalanarak Çoğaltılan Aktiviteler adında yeni bir boş solution oluşturalım. Şimdi de C:\Users\Soner\Documents\Visual Studio 2010\Projects\Siparisİslemi altındaki Siparisİslemi klasörünü az önce oluşturduğumuz Kopyalanarak Çoğaltılan Aktiviteler klasörünün içine kopyalayalım.

WF-Folder

Solution Explorer kısmından Add->Existing Project diyerek Kopyalanarak Çoğaltılan Aktiviteler/Siparisİslem klasörünün içinde bulunan Siparisİslemi.csproj dosyamızı ekleyelim.

SiparişÜrün İşlemini Ekleyelim

ForEach aktivitesi Collection kısmındaki her bir madde için bir aktivite çalıştırır. Bu proje için bizim tam da istediğimiz budur aslında.

SiparisWF.xaml dosyamızı dizayn modunda açalım.  “İlk Toplam” aktivitemizin altına bir adet ForEach<T> aktivitesi sürükleyip bırakalım. DisplayName özelliğini Sipariş Ürünlerini Biriktirme olarak adlandıralım. Expression özelliğine SiparisBilgisi.Ogeler yazalım. Fakat bunu yazdığımızda bir hata alırız. Aktivitenin <T> olarak tanımlanması bu aktivitenin generic sınıf olduğunu, bu yüzden tip olarak collection içeren bir tip tanımlamamız gerektiğini bize gösterir. Varsayılan tip olarak Int32 seçildiğinden SiparisBilgisi.Ogeler ifadesi de bir Int32 tipinde collection içermediğinden bu hatayı alırız. Bu yüzden ArgumentType seçeneğini Browse Type diyerek Siparisİslemi.SiparisOge olarak belirtmeliyiz.

WF-BrowseType2

Bu tipi seçtiğimizde artık SiparisBilgisi.Ogeler değeri Siparisİslemi.SiparisOge tipinde bir collection içerir. (Siparis.cs içerisinde yazdığımız kod nedeniyle) Hata ortadan kalkmış olur. Şimdi ForEach<T> aktivitesi içerisine bir Assign aktivitesi sürükleyip bırakalım.

NOT: Foreach<T> aktivitesi içerisine sadece bir basit aktivite sürükleyebiliriz. Eğer birden fazla aktiviteye ihtiyacınız olursa, içerisine bir Sequence aktivitesi sürükleyip diğer aktiviteleri bunun içerisine yerleştirebilirsiniz.

Assign aktivitemizin To özelliğini ToplamMaliyet, Value özelliğini de ToplamMaliyet + (item.Miktar * 10.0D) olarak atayalım. Bu işlem sipariş içerisindeki her miktar için toplam maliyete 10 ekler.

Şimdi “Sipariş Ürünlerini Biriktirme” aktivitesinin altına bir Writeline aktivitesi ekleyelim. DisplayName özelliğine “Toplam Maliyeti Göster”, Text özelliğine de “Ürün Toplamı: ” + ToplamMaliyet.ToString() ifadesini atayalım. Bu sipariş edilmiş ürünlerin toplamını, taşıma ücretini eklemeden önce gösterir.

Sipariş Ürünlerini Ekleme

Şimdi bu özelliği test etmeden önce Sipariş sınıfı içerisindeki SiparisOge nesnelerini düzenlememiz lazım. Program.cs dosyamızı açalım ve aşağıdaki kodumuzu siparisim nesnesini oluşturduktan hemen sonra yazmaya başlayalım.


siparisim.Ogeler.Add(new SiparisOge
{
SiparisOgeID = 1,
Miktar = 1,
OgeID = "12345",
Tanım = "Alet"
});

siparisim.Ogeler.Add(new SiparisOge
{
SiparisOgeID = 2,
Miktar = 3,
OgeID = "12346",
Tanım = "Küçük Alet"
});

siparisim.Ogeler.Add(new SiparisOge
{
SiparisOgeID = 3,
Miktar = 2,
OgeID = "12347",
Tanım = "Süper Alet"
});

Şimdi F5 ile projemizi çalıştıralım.

Sipariş Alındı
Ürün Toplamı: 60
Toplam Maliyet: 120,0
Workflow’un döndürdüğü Toplam Maliyet 120,0
Enter to Exit

Peki bu nasıl hesaplandı?

SiparisOge sınıfının miktar olarak sırasıyla 1, 3, 2 ogeler mevcut. Bunların toplamı 6 yapıyor. Her biri için 10 eklediğimizi hatırlayalım. Bu da toplamı 60 yapar. Bir önceki yazıda gördüğümüz üzere Taşıma Ücreti’de 60 olarak hesaplamıştık. Bu ikisinin toplamı da sonuçta gördüğümüz gibi 120 değerini elde ediyoruz.

Tüm Program.cs kodumuz aşağıdaki şekildedir;


using System;
 using System.Linq;
 using System.Activities;
 using System.Activities.Statements;
 using System.Collections.Generic;

namespace Siparisİslemi
 {

class Program
 {
 static void Main(string[] args)
 {
 Siparis siparisim = new Siparis
 {
 SiparisID = 1,
 Tanım = "Sipariş Tanımı",
 SiparisMetodu = "İkiSonrakiGün",
 ToplamAgirlik = 100,
 };

siparisim.Ogeler.Add(new SiparisOge
 {
 SiparisOgeID = 1,
 Miktar = 1,
 OgeID = "12345",
 Tanım = "Alet"
 });

siparisim.Ogeler.Add(new SiparisOge
 {
 SiparisOgeID = 2,
 Miktar = 3,
 OgeID = "12346",
 Tanım = "Küçük Alet"
 });

siparisim.Ogeler.Add(new SiparisOge
 {
 SiparisOgeID = 3,
 Miktar = 2,
 OgeID = "12347",
 Tanım = "Süper Alet"
 });

//Workflow için input argümanlarını oluşturalım
 IDictionary<string, object> input = new Dictionary<string, object>
 {
 {"SiparisBilgisi", siparisim}
 };

//Workflow'u çalıştıralım
 IDictionary<string, object> output = WorkflowInvoker.Invoke(new SiparisWF(), input);

//Workflow'dan geri dönen ToplamMaliyet'i alalım
 decimal toplam = (decimal)output["ToplamMaliyet"];
 Console.WriteLine("Workflow'un döndürdüğü Toplam Maliyet {0}", toplam);
 Console.WriteLine("Enter to Exit");
 Console.ReadLine();
 }
 }
 }

ParallelForEach Aktivitesi

ForEach aktivitesi yerine ParallelForEach aktiviteisi de kullanabilirdik. İki aktivite de aynı şekilde yapılandırılmıştır. Tek fark aktivitelerin nasıl çalıştırıldığıdır.

ParallelForEach’in adından da anlaşılacağı gibi alt aktiviteler eşzamanlı olarak çalışırlar. ForEach aktivitesinde ise alt aktiviteler sıralı olarak çalışırlar.

Posted in WF.

Bu yazıda Workflow Foundation ile Kopyalanarak Çoğaltılan Aktiviteler yazımızda yaptığımız satış uygulamasına ek olarak bu sefer eğer ürünümüz stokta yoksa workflow tarafından yakalanacak olan bir exception fırlatacağız.

Visual Studio’yu açıp yeni bir Blank solution oluşturalım. İsmini de “Exception Handling” olarak ayarlayalım.

WF-BlankSolution

Workflow Foundation ile Kopyalanarak Çoğaltılan Aktiviteler yazımızda oluşturduğumuz projenin altındaki Siparisİslemi klasörünü Exception Handling klasörünün altına kopyalayalım. Daha sonra da Solution Explorer kısmından Add->Existing Project diyerek Exception Handling klasörünün altındakiSiparisİslemi klasörü içinde bulunan Siparisİslemi.csproj dosyasını seçelim.

Şimdi SiparisWF.xaml dosyamızı dizayn modunda açalım. “Kullanım Ücreti” switch aktivitesinin altına bir TryCatch aktivitesi sürükleyip bırakalım. DisplayName özelliğini “Stok Kontrolü” olarak atayalım.

TryCatch aktivitesi üç kısımdan oluşmaktadır. Try kısmında, potansiyel olarak bir exception üretebilecek aktiviteler yer alır. Catches kısmında, bir veya daha fazla Catch nesnesi tanımlayabiliriz. Her Catch nesnesi belirli bir exception işler. Finally kısmı opsiyoneldir. Try aktivitesinden sonra işleyeceğimiz aktiviteler varsa bu aktiviteleri bu kısımda işleyebiliriz.

Şimdi bir ürünün stokta olmayışı durumudan kullanacağımız exception’ı tanımlamaya başlayabiliriz. Siparis.cs dosyamızı açalım ve bir StoktaYokException (isim biraz saçma evet) sınıfı yaratalım. Siparis sınıfından sonra aşağıdaki kodlarımızı yazalım. (Yazdığımız kodun Siparisİslemi namespace’i içinde olmasına dikkat edelim.)


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities.Expressions;
using System.Activities.Statements;

namespace Siparisİslemi
{
public class SiparisOge
{
public int SiparisOgeID { get; set; }
public int Miktar { get; set; }
public string OgeID { get; set; }
public string Tanım { get; set; }
}

public class Siparis
{
public Siparis()
{
Ogeler = new List<SiparisOge>();
}
public int SiparisID { get; set; }
public string Tanım { get; set; }
public decimal ToplamAgirlik { get; set; }
public string SiparisMetodu { get; set; }

public List<SiparisOge> Ogeler { get; set; }
}

//---------------------------------------------
// Eğer bir ürün stokta yoksa oluşacak exception durumunu tanımlama
//---------------------------------------------

public class StoktaYokException : Exception
{
public StoktaYokException()
: base()
{
}

public StoktaYokException(string mesaj)
: base(mesaj)
{
}
}
}

Try kısmına bir ForEach aktivitesi sürükleyip bırakalım. DisplayName özelliğine “Her Ürünü Kontrol Et” olarak atayalım. Expression özelliğine de SiparisBilgisi.Ogeler ifadesini yazalım. TypeArgument özelliğine de Browse for Types diyerek Siparisİslemi.SiparisOge olarak atayalım.

Bu ForEach aktivitesi içine bir If aktivitesi ekleyelim. DisplayName özelliğini “Eğer Stokta Yoksa” olarak atayalım. Condition kısmına item.OgeID = “12346″ ifadesini yazalım. Bu işlemin ardından Then kısmına bir Throw aktivitesi sürükleyelim. Bu Throw aktivitesinin Exception özelliğine aşağıdaki ifademizi yazalım:


New Siparisİslemi.StoktaYokException("Ürün Kodu: " + item.OgeID)

Bu ifade kısaca yeni bir StoktaYokException oluşturur ve parametre olarak aldığı mesajda hangi ürün stokta kalmadıysa onun kodunu gösterir.

Catches kısmından “Add new catch” linkine tıklayalım. Karşımıza genel olarak tanımlanan exception’ların bir listesi drop-down olarak görülecektir. Browse for Types diyerek Siparisİslemi.StoktaYokException istisna durumunu seçelim.

WF-Catch

Yakaladığımız her exception durumu için, çalışabileceğimiz bir aktivite tanımlamaya ihtiyacımız olacaktır. Catches kısmının içine bir Writeline aktivitesi ekleyelim ve Text özelliğine

“Ürün stokta yok – ” + exception.Message

ifadesini ekleyelim. Bu kod Exception’dan gelen (stokta olmayan) ürün kodunu içeren mesajı ifade eder. “Stok Kontrolü” aktivitesinin son hali aşağıdaki şekilde gözükmelidir derleyicimizde;

WF-TryCatch

Burada Try bölümü “Her Ürünü Kontrol Et” adında bir aktivite olduğunu gösterir bize. Catches kısmında bize yakalacağımız istisna durumlarını belirtir. Ki burada sadece bir adet istisna durumumuz (StoktaYokException) olduğunu ve bunu bir Writeline aktivitesi ile belirttiğimizi görürüz.

Şimdi projemizi F5 ile çalıştıralım.

Sipariş Alındı
Ürün Toplamı: 60
Ürün stokta yok – Ürün Kodu: 12346
Toplam Maliyet: 120,0
Workflow’un döndürdüğü Toplam Maliyet 120,0
Enter to Exit

Posted in WF.

Daha önceki yazılarımızda workflow içerisinde argüman ve değişkenlerin nasıl tanımlandığını göz attık. Benzer olarak, değişkenler tıpkı sınıf üyeleri gibi, argümanlar ise tıpkı method parametrelerine benzerler. Önceki yazılarımızda değişkenleri kullanmıştık. Bu yazımızda input ve output argümanlarını ve bunların kullanımlarını göreceğiz.

Hemen Siparisİslemi adında bir Workflow Console Application oluşturalım.

WF-Siparisİslemi

Bu projemizde bazı ürünler için bir sipariş oluşturacağız ve bu siparişi workflow’umuzun içerisine geçireceğiz. Daha sonra workflow’umuz toplam maaliyeti hesaplayacak ve uygulamaya döndürecektir.

Şimdi Solution Explorer kullanarak projemize sağ tıklayıp Add –> Class seçeneğinden projemize Siparis.cs adındaki sınıfımızı ekleyelim.

WF-SiparisSolution

Siparis adındaki sınıfımıza siparis detaylarını içeren tanımlamaları yapmaya başlayalım.


namespace Siparisİslemi
{
public class SiparisMaddeleri
{
public int SiparisOgeID { get; set; }
public int Miktar { get; set; }
public int OgeID { get; set; }
public string Tanım { get; set; }
}

public class Siparis
{
public Siparis()
{
Ogeler = new List<SiparisMaddeleri>();
}
public int SiparisID { get; set; }
public string Tanım { get; set; }
public decimal ToplamAgirlik { get; set; }
public string SiparisMetodu { get; set; }

public List<SiparisMaddeleri> Ogeler { get; set; }
}
}

 

Siparis sınıfı birkaç public üyeye sahiptir (SiparisID, Tanım, ToplamAgirlik, SiparisMetodu) ve bir adet SiparisMaddeleri sınıfı listesine sahiptir. Bunlar sipariş maaliyetlerini ölçeceğimiz workflow’umuzun detaylarıdır.

Ardından projemizde bulunan Workflow1.xaml dosyasının ismini SiparisWF.xaml olarak değiştirelim. Ayrıca bu xaml dosyasını “View Code” ile açarak ilk satırdaki x:Class=”Siparisİslemi.Workflow1″ sınıf niteliğini x:Class=”Siparisİslemi.SiparisWF” olarak değiştirelim.

SiparisWF.xaml dosyamızı dizayn modunda açalım. Ardından sol alt taraftaki Arguments kısmına tıklayıp argümanlarımızı ekleyeceğimiz pencereyi açalım.

NOT: Argümanlarda, değişkenlerden farklı olarak Scope kısmı bulunmaz. Bunun nedeni, değişkenlerin workflow’un tümünde ya da belirli aktivitelerde tanımlanmasına karşın, argümanların tanımlanması tüm workflow içindir çünkü bu yapılar workflow’a ya da workflow’dan veri geçişini tanımlarlar.

Açtığımız pencereden Create Argument’e tıkayıp argümanımızın ismini SiparisBilgisi olarak değiştirelim. Direction kısmı In olarak kalsın. Argument Type seçeneğinde ise Browse Type seçeneğinden Siparisİslemi.Siparis tipini seçelim. Eğer bu tip Argument Type kısmında gözükmüyorsa projemizi bir kere Build Solution ile build etmeliyiz.

WF-BrowseType

İkinci bir argümanımızı ToplamMaliyet adında tanımlayalım. Direction kısmını Out, ArgumentType kısmını Decimal olarak taımlayalım. İlk kez kullandığınız için Decimal tipi açtığınız drop-down’da yer almaz. Bu tip mscorlib assembly içerisinde System namespace’i altındadır. (System.Decimal)

Şimdi Workflow’umuzu tasarlayalım. Bir adet Sequence aktivitesi sürükleyip bırakalım. İçine de bir Writeline aktivitesi sürükleyip Text özelliğine “Sipariş Alındı” yazalım. Bu aktivitenin altına bir Assign aktivitesi sürükleyelim. DisplayName özelliğini İlk Toplam, To özelliğini ToplamMaliyet ve Value özelliğini de 0 (sıfır) olarak atayalım. Bu aktivite kısaca toplam maliyeti sıfır olarak ilk kullanıma hazırlar.

Switch Aktivitesi

Switch aktivitesi tıpkı C#’taki switch ifadesine benzer şekilde çalışır. İçerisinde çalıştırılan ifadeye göre belirli aktiviteleri çalıştırılmasına olanak verir. Bu aktiviteyi SiparisMetodu hesaplamak için kullanacağız. Switch aktivitesi Toolbox içerisinde Switch<T> olarak bulunur. Assign aktivitesi altına bir adet Switch<T> aktivitesi sürükleyip bırakalım. Sürükleyip bıraktığımızda bu aktivite bizden içerisinde çalıştıracağımız SiparisMetodu string olduğu için bu aşamada tipimizi String olarak seçelim. DisplayName özelliğini Kullanım Ücreti olarak değiştirelim.

WF-Switch

Switch aktivitesi bir Expression özelliğine sahiptir. Bu özelliği SiparisBilgisi.Siparis olarak tanımlayalım. Daha sonra Add new case seçeneğinden BirSonrakiGün ve İkiSonrakiGün adında iki adet durum ekleyelim.

Expression Aktiviteleri

Şimdiye kadar projemizde BirSonrakiGün ve İkiSonrakiGün adında SiparisMetodu sonucuna göre iki adet case oluşturduk. Bir de bunun yanında diagramda görüleceği gibi default durumumuz mevcut. Şimdi de bu spesifik durumlara göre aktiviteler tanımlayalım. Bu durumumuzda Add aktivitesini kullanacağız.

NOT: System.Activities.Expressions namespace’i workflow’unuzda kullanabileceğiniz birçok aktivite içerir. (Add, Subtract, Multiply ve Divide gibi) Ayrıca expression’ları değerlendirmek için Equal, GreaterThan, And ve Or gibi lojik aktiviteler de içerir.

Ne yazık ki, Add aktivitesi Toolbox’ta mevcut değil. Bu aktiviteyi .xaml dosyamızın kod kısmından elle eklememiz gerekecektir. XAML dosyamızı “View Code” seçeneği ile açalım. Switch aktivitesi aşağıdaki şekilde görülecektir;


<Switch x:TypeArguments="x:String" DisplayName="Kullanım Ücreti" Expression="[SiparisBilgisi.SiparisMetodu]" sap:VirtualizedContainerService.HintSize="473,151">
<x:Null x:Key="BirSonrakiGün" />
<x:Null x:Key="İkiSonrakiGün" />
</Switch>

Görüldüğü üzere, BirSonrakiGün ve İkiSonrakiGün case’leri için x:Null durumu görülür. Bunun anlamı, bu durumlar için herhangi bir aktivite henüz tanımlanmamıştır. Şimdi kod ile Add aktivitemizi tanımlayalım. Yukarıdaki <x:Null ile başlayan iki satırı silip aşağıdaki Add aktivitemizi içeren kodlarımızı yazalım.


<Add x:TypeArguments="s:Decimal, s:Decimal, s:Decimal" x:Key="BirSonrakiGün"
DisplayName="15 Ekle" Left="[ToplamMaliyet]" Result="[ToplamMaliyet]"
Right="[15.0D]" />
<Add x:TypeArguments="s:Decimal, s:Decimal, s:Decimal" x:Key="İkiSonrakiGün"
DisplayName="10 Ekle" Left="[ToplamMaliyet]" Result="[ToplamMaliyet]"
Right="[10.0D]" />

Bu adımdan sonra da default durumu için aşağıdaki kodumuzu tanımlayalım;


<Switch.Default>
<Add x:TypeArguments="s:Decimal, s:Decimal, s:Decimal" DisplayName="5 Ekle"
Left="[ToplamMaliyet]" Result="[ToplamMaliyet]" Right="[5.0D]" />
</Switch.Default>

Add aktivitemizin Left, Right ve Result adında 3 adet özelliği vardır. Özet olarak, Left özelliğine Right özelliği eklenir ve toplam Result özelliğinde saklanır. Left ve Result özellikleri ToplamMaliyet argümanına ayarlanır. Right özelliği statik bir değere sahiptir ve her durum için farklıdır. Switch aktivitemizin son hali şu şekildedir;


<Switch x:TypeArguments="x:String" DisplayName="Kullanım Ücreti" Expression="[SiparisBilgisi.SiparisMetodu]" sap:VirtualizedContainerService.HintSize="473,151">
<Switch.Default>
<Add x:TypeArguments="s:Decimal, s:Decimal, s:Decimal" DisplayName="5 Ekle"
Left="[ToplamMaliyet]" Result="[ToplamMaliyet]" Right="[5.0D]" />
</Switch.Default>
<Add x:TypeArguments="s:Decimal, s:Decimal, s:Decimal" x:Key="BirSonrakiGün"
DisplayName="15 Ekle" Left="[ToplamMaliyet]" Result="[ToplamMaliyet]"
Right="[15.0D]" />
<Add x:TypeArguments="s:Decimal, s:Decimal, s:Decimal" x:Key="İkiSonrakiGün"
DisplayName="10 Ekle" Left="[ToplamMaliyet]" Result="[ToplamMaliyet]"
Right="[10.0D]" />
</Switch>

Kısacası, SiparisMetodu, BirSonrakiGün olduğunda ToplamMaliyet değerine 15 eklenir, İkiSonrakiGün olduğunda 10 eklenir, diğer durumlarda ise default kısım çalıştırılacağından 5 eklenir. Switch aktivitemizi genişlettiğimizde aşağıdaki şekilde görülür.

WF-AddinSwitch

NOT: Add aktivitemizin Right seçeneğinin 15.0D olarak tanımlanmasının nedeni Visual Basic syntax’ında Decimal değerlerinin bu şekilde gösterimi olduğundandır.

Şimdi Switch aktivitemizin altına bir Assign aktivitesi ekleyelim ve DisplayName özelliğini Taşıma Ücreti, To özelliğini ToplamMaliyet, Value özelliğini de ToplamMaliyet + (SiparisBilgisi.ToplamAgirlik * 0.5D) olarak ayarlayalım. Bu formül kısaca Taşıma Ücretine, her toplam ağırlık için 0.5 değer katar.

Ardından Assign aktivitesinin altına bir Writeline aktivitesi sürükleyip bırakalım. Text özelliğine “Toplam Maliyet: ” + ToplamMaliyet.ToString() ifadesini ekleyelim.

Tasarım kısmında herşeyimiz tamam. Şimdi Program.cs içerisinde workflow’umuzu çağıracağımız kodlarımızı yazalım.


using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;
using System.Collections.Generic;

namespace Siparisİslemi
{

class Program
{
static void Main(string[] args)
{
Siparis siparisim = new Siparis
{
SiparisID = 1,
Tanım = "Sipariş Tanımı",
SiparisMetodu = "İkiSonrakiGün",
ToplamAgirlik = 200,
};

//Workflow için input argümanlarını oluşturalım
IDictionary<string, object> input = new Dictionary<string, object>
{
{"SiparisBilgisi", siparisim}
};

//Workflow'u çalıştıralım
IDictionary<string, object> output = WorkflowInvoker.Invoke(new SiparisWF(), input);

//Workflow'dan geri dönen ToplamMaliyet'i alalım
decimal toplam = (decimal)output["ToplamMaliyet"];
Console.WriteLine("Toplam Maliyet {0}", toplam);
Console.WriteLine("Enter to Exit");
Console.ReadLine();
}
}
}

Bu kod kısaca Siparis sınıfı oluşturur ve bu sınıfı bazı verilerle doldurur. Sonra bir Dictionary nesnesi oluşturur ve Siparis nesnesini bunun içinde muhafaza eder. WorkflowInvoker sınıfının statik Invoke() metodunu çağırır. Invoke() metodu uygulama içerisinde bir workflow örneği oluşturur ve çalıştırır.

Bunu bir Dictionary nesnesi olarak geçirmemiz bize birden fazla parametre geçişine olanak verir. Invoke() metodu geriye bir Dictionary nesnesi (In/Out Direction ile tüm workflow argümanlarını içeren) döndürür. Toplam Maliyet argümanı Dictionary içerisinden çıkartılır ve ekrana yazdırılır. Şimdi uygulamamızı F5 ile çalıştıralım.

Sipariş Alındı
Toplam Maliyet: 110,0
Workflow’un döndürdüğü Toplam Maliyet 110,0
Enter to Exit

 

Peki doğru mu hesapladı?

Toplam ağırlığımızı 200 olarak verdiğimizden bu değeri 0.5 ile çarpıp 100 değerini elde ettik. Buna da “İkiSonrakiGün”’ seçeneğinin Right özelliğindeki değer 10.0 olduğu için bu iki değerin toplamını yani 110 değerini elde ettik.

Posted in WF.

FlowSwitch aktivitesi tıpkı FlowDecision aktivitesine benzer şekilde çalışır fakat farklı olarak FlowDecision aktivitesinde True ya da False şeklinde 2 adet dal tanımlanmasına karşın, bu yapıda sınırsız sayıda dal tanımlayabilirsiniz. C#’ta ve bir çok programlama dilinde kullandığımız switch yapısına benzer.

Bu yazıda WF ile FlowChart Aktivitesi Oluşturmak makalemizde kullandığımız projemiz üzerinden devam edeceğiz. Projemizin alt tarafına bir FlowSwitch aktivitesi sürükleyip bırakalım. Toolbox kısmında FlowSwitch<T> şeklinde görünmektedir bu aktivite. Sürüklediğinizde sizden içereceği daha tipini gerektiren bir mesaj kutusu görünür. Burada default olarak Int32 görünür ki bizim de bu projemizde kullancağımız data tipi budur. Ardından daha önceden projemize eklediğimiz “Sabah”, “Öğleden Sonra” ve “Akşam” isimli Writeline aktivitelerini FlowSwitch aktivitemize bağlayalım.

WF-FlowSwitch

FlowSwitch aktivitesinin bir adet Expression adından bir özelliği vardır. Burada yazılan koşul, bu aktivitenin birden fazla dalındaki değerleri çözmek için kullanılır. (Tıpkı programlama’da olduğu gibi) Şimdi bu FlowSwitch aktivitemizin Expression özelliğine koşulumuzu yazalım;


CInt(((DateTime.Now.Month Mod 12) + 1) / 4)

NOT: Burada yazdığımız ifade sizi biraz şaşırtabilir. WF 4.0’da yazılan tüm ifadeler Visual Basic syntax’ı ile yazılırlar. Bu ifadeler derlenmezler, sadece workflow aktiviteleri tarafından hesaplanırlar. Böylece bu ifadelerin syntax’ı, uygulamalarda kullandığınız programlama dilinden bağımsızdır. İfade yazacağınız kutunun üzerine geldiğinizde “Enter a VB expression” yazısını görürüz.

Yukarıda yazdığımız ifade, yaklaşık olarak şu andaki zamanın mevsim değerini ifade eder. Aralık, Ocak ve Şubat ayları için 0 değerini üretir, Mart, Nisan, Mayıs için 1 değerini üretir. Şimdi bu aktivitemizde her bir mevsim için bir adet dal (branch) üretiriz.

FlowSwitch aktivitemizin her bir dalına FlowStep adı verilir. Tabi Toolbox’ta FlowStep aktivitesi bulunmaz. Bu dalları açıkça workflow’a eklemezsiniz. Bu dallar FlowSwitch aktivitesinden bağlantılar çizdiğinizde oluşturulurlar.

Şimdi FlowSwitch aktivitemizin etrafına 5 adet Writeline aktivitesi sürükleyip bırakalım. Bunlardan dördünü mevsimler için birini de default değer için kullanacağız. FlowSwitch aktivitesi ile bu 5 Writeline aktivitesini birbirlerine bağlayalım. Daha sonra da bu bağlantı dallarının üzerlerine tıklayıp Properties penceresinden Case özelliğine, ifade sonucunun hangi bağlantıyı seçeceğine dair Int32 değerlerimizi girelim. Default Writeline aktivitesini boş bırakalım ve bu bağlantının IsDefaultCase özelliğini tikleyelim.

WF-FlowStep

Tabi bu diagramımızda Default aktivitesi hiçbir zaman çalıştırılmaz çünkü olası her değer için bir Writeline aktivitesi tanımlamış durumdayız. Projemizi F5 ile çalıştırdığımızda aşağıdakine benzer sonuç elde ederiz;

Merhaba Dünya
İyi Öğle Sonraları
Selam Yaz
ENTER to exit

 

Parallel Aktivitesini Tanıyalım

Parallel aktivitesi, birçok aktivitenin paralel olarak çalıştırılmasına imkan veren bir aktivitedir. Bu projemizde her bir branch belirli bilgiler gösterirler. Gösterdikleri bilgilerin sıraları önemli değildir, bu yüzden bunları bir Parallel aktivite içerisine koyarız.

Şimdi bir Parallel aktivitesini Toolbox’tan workflow’umuzun altına sürükleyip bırakalım. Daha önce eklediğimiz 5 Writeline aktivitesini bu Parallel aktivitesine bağlayalım.

WF-Parallel

Ardından Parallel aktivitemize çift tıklayıp içerisine 3 adet Writeline aktivitesi ekleyelim. Bu aktiviteleri sırayla zamanı, tarihi ve günü göstermek için kullanacağız. Bu yüzden bu aktivitelerin Text özelliklerine sırasıyla;


"Zaman: " + DateTime.Now.TimeOfDay.ToString()

"Tarih: " + DateTime.Now.Date.ToShortDateString()

"Bugün: " + DateTime.Now.ToString("dddd")

ifadelerini yazalım.

WF-Parallel2

NOT: Parallel aktivitesi kendisine bağlanan her bir branch için basit bir adet aktivite destekler. Bizim projemiz için şu anda herhangi bir sorun yok. Fakat, her dal için birden fazla aktiviteye ihtiyacınız varsa,  Sequence aktivitesi kullanmanız gerekecektir. Sonra da içine herhangi bir sayıda aktivite ekleyebilirsiniz.

Şimdi de projemizi F5 ile çalıştıralım. Aşağıdakine benzer bir sonuç almalıyız;

Merhaba Dünya
İyi Öğle Sonraları
Selam Yaz
Zaman: 17:18:07.3144532
Tarih: 05.08.2012
Bugün: Pazar
ENTER to exit

Bir sonraki yazımızda görüşmek üzere..

Posted in WF.

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.

Posted in WF.

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.

Posted in WF.