Tag Archives: out

Workflow Foundation ile Argüman Geçişleri

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.

C#’ta Struct Yapısı

Sınıflar gibi, struct yapıları veri ve fonksiyon üyeleri içeren veri yapılarıdır. Sınıflardan farklı olarak, struct yapısı değer tipidir ve heap bölge tahsisi gerektirmez. Bir struct değişkeni, direkt olarak struct’ın verisini tutar. Oysa sınıf tipinde bir değişken, dinamik olarak ayrılmış nesneye referans tutar. Struct yapısı, kullanıcı tanımlı kalıtımı desteklemez ve tüm struct yapıları dolaylı olarak object tipinden kalıtım alır.

Önemli: Struct yapılarının heap bölge tahisisi gerektirmemesi, bu yapıların hiçbir zaman bunu yapmadıkları anlamına gelmez.

Bu yapılar özellikle küçük veri yapıları kullanımında kullanışlı olurlar. Karmaşık sayılar ve koordinat sisteminde noktalar struct yapılarına iyi örneklerdir. Bir yapıda sınıf yerine struct kullanımı, bellek tahsis etmede ve programınızın performansında büyük farklılıklar yaratabilir.

Örneğin; aşağıdaki program 100 nokta oluşturan bir programdır. Sınıf yapısı ile kullandığımızda 101 adet farklı nesne – 1 adet dizi için, 100 adet te dizi üyeleri için – oluşturulur:

using System;


class Nokta

{

    public int x, y;


    public Nokta(int x, int y)

    {

        this.x = x;

        this.y = y;

    }

}

class Test

{

    static void Main()

    {

        Nokta[] noktalar = new Nokta[100];

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

            noktalar[i] = new Nokta(i, i);

    }

}

struct-1

Alternatif olarak Nokta yapısını struct yapabiliriz.

struct Nokta

{

    public int x, y;


    public Nokta(int x, int y)

    {

        this.x = x;

        this.y = y;

    }

}

Burada sadece 1 nesne yaratılır – 1 adet dizi için – ve Nokta örnekleri dizi içerisinde saklanırlar.

struct-2

Önemli: Struct yapısının performans açısından sağladığı faydayı “Her zaman struct kullanın” şeklinde algılamak yanlış olur. Tabi ki bazı senaryolarda struct yapısına bellek ayırmak ve bellekten almak daha az zaman alır fakat her struct ataması bilindiği gibi değer kopyalamasıdır (value copy). Bu her zaman referans kopyalamasından daha fazla zaman alır.

Struct yapıcıları new operatörü ile çağrılırlar fakat bu demek değildir ki belirli bir bellek ayrılmıştır. Nesnenin veya referansının dinamik olarak bellek ayrımı yerine, bir struct yapıcısı basitçe struct değerinin kendisini döndürür (genellikle yığının geçici bölgesinde) ve bu değer gereli olduğunda kopyalanır.

Sınıflarda, iki farklı değişken aynı nesneye referans gösterebilir ve bu şekilde bir değer üzerindeki işlemler aynı nesneyi referans eden diğer değişkeni etkileyebilir. Struct yapılarında, her değişken kendi verisinin kopyasını tutar ve bir değişkenin diğerini etkilemesi imkansızdır. Örneğin aşağıdaki kodu inceleyelim;

Nokta a = new Nokta(10, 10);

Nokta b = a;

a.x = 20;

Console.WriteLine(b.x);

Eğer buradaki Nokta örneği bir sınıf (class) ise, sonucumuz 20 olur çünkü a ve b aynı nesneye referans gösterirler. Eğer Nokta örneği bir struct ise, sonucumuz 10 olur çünkü ikinci satırdaki atama işlemi değer olarak a’daki değerleri b’ye kopyalar ve bu kopyalama 3. satırdaki a.x atamasını etkilemez.

Bu örnekte struct kullanımının 2 noktasının farkına vardır. Birincisi, tüm struct yapısını kopyalamak bir nesne referansını kopyalamaktan daha az verimli. Böylece atama yapma ve değer parametresi geçirme struct yapılarında referans tiplerine göre daha maliyetli olabilir. İkincisi, ref ve out parametreleri dışında, struct yapısına referans oluşturma imkansızdır.

Bill Wagner: Eğer tüm durumlarda değer anlamı (value semantics) istemiyorsanız, sınıf kullanmalısınız. Sınıflar bazı durumlarda değer anlamı uygulayabilirler, (string gibi) ama default olarak referans anlamına uyarlar. Bu fark sizin tasarımınızda yığın vs hesap bellek ayrımına göre daha çok farklılık gösterir.

C#’ta Method Parametreleri

Parametreler, bir değere atanan ya da bir methoda referans eden değişkenler için kullanılır. Method parametreleri, method çağırıldığında, asıl değerlerini argümanlardan alırlar. 4 çeşit parametre tipi bulunur; değer parametreleri (value parameters), referans parametreleri (reference parameters), çıktı parametreleri (output parameters) ve parametre dizileri (parameters arrays).

Değer parametreleri (value parameters), girdilerde parametre geçirilmelerinde kullanılır. Bir değer parametresi, methodlara geçirilen argümanların ilk değerlerini içeren yerel değişkenler ile uyuşur.

Değer parametrelerindeki değişiklikler, parametreye geçirilen argümanları etkilemezler.

Önemli: Değer parametrelerindeki değişiklik durumları, argümanları etkilemezler kelimeleri yanıltıcı olabilir çünkü türetilmiş sınıflar referans tip’te parametre içeriğini değiştirebilir. Parametre değerleri değiştiremez fakat referans edilmiş içerik (nesne) değiştirebilir.

Değer parametreleri seçimli olabilir, varsayılan değeri belirlemek için, böylece argümanlara ilişkilendirme yapılabilir.

Referans parametreleri, hem girdi (input) hem de çıktı (output) parametre geçişleri için kullanılır. Referans parametrelerini geçiş argümanları; bir değişken, yürütme süresinceki methodlar, ayrıca referans parametreleri argüman değişkenleri ile  aynı bellek bölgesini simgelerler. Bir referans parametresi ref anahtar sözcüğü ile gösterilir. Aşağıdaki örnek; ref anahtar sözcüğünün kullanımını gösterir.

class Test
{
static void Degistir(ref int x, ref int y)
{
int temp = x;
x =  y;
y = temp;
}
static void Main()
{
int i = 10, j = 20;
Degistir(ref i, ref j);
Console.WriteLine("{0} {1}", i, j); // Çıktı 20 10 olur.
}
}

Bu örneği ref anahtar sözcüksüz yazsaydık, çıktı “10 20” olurdu. Yani o değişkenlere referans vermeden bunların yerleri değiştirilemezdi.

Önemli: Referans parametreleri C# tarafından “referans ile geçiş” olarak adlandırılır; önce bir nesne örneğini bir methoda geçirir ve o method o nesne örneğine bir referans alır.

Referans parametreleri, çok küçük bir farkla “referans ile geçiş”’ten farklıdır. Bu durumda, referans bir değişkenin kendisindedir, bir nesne örneğinde değil. Eğer bu değişken, bir değer tipi içerirse bu tamamen normal bir durumdur. Bu değere “referans ile geçiş” yapılmamıştır değişken bu değeri tuttuğu sürece.

Referans parametrelerini düşünmenin iyi bir yolu, referans parametreleri, değişkenleri argümanlara geçiştirmek için bir takma ad’a dönüşürler şeklinde düşünmektir. Yukardaki örnekte, x ve i aslında aynı değişkendirler. Bellekte aynı bölgeye kaynak gösterirler.

Çıktı parametreleri (Output parameters), çıktı parametre geçişleri için kullanılırlar. Çıktı parametreleri referans parametrelerine benzerdir, fark olarak burada değeri ilk çağıran-sağlayan argüman önemsizdir. Çıktı parametreleri, out anahtar sözcüğü ile gösterilirler. Aşağıdaki örneği inceleyebiliriz;

using System;

namespace HelloWorld
{
class Test
{
static void Bolme(int x, int y, out int sonuc, out int kalan)
{
sonuc = x / y;
kalan = x % y;
}
static void Main()
{
int son, kal;
Bolme(20, 3, out son, out kal);
Console.WriteLine("{0} {1}", son, kal); // Çıktı 6 2 olur.
}
}
}

Önemli: CLR, direkt olarak sadece ref parametrelerini destekler. Out parametresi metadata içerisinde temsil edilir.

Parametre dizileri, bir methoda birden fazla sayıda argüman değişkeninin geçirilmesine izin verirler. Parametre dizileri, params anahtar sözcüğü ile gösterilirler. Bir methodun sadece son parametresi bir parametre dizisi olabilir ve parametre dizisinin tipi tek boyutlu dizi tipi olmak zorundadır. System.Console sınıfının Write ve Writeline methodları parametre dizilerinin kullanımına iyi bir örnektir. Bu iki method şu şekilde tanımlanmıştır:

 public class Console
{
public static void Write(string fmt, params object[] args) {...}
public static void WriteLine(string fmt, params object[] args) {...}
} 

Bu tür method tanımlamalarına, Visual Studio içerisinde methodun üzerine gelip sağ tıkladığımızda “Go to definition” sekmesiyle ulaşabiliriz. (Kısayol F12)

 

 

 

 

 

 

 

Parametre dizileri içeren bir method içerisinde, parametre dizileri bir dizi tipinin parametreleri ile tam olarak aynı şekilde davranır. Fakat, bir parametre dizisine sahip method, hem parametre dizisi tipindeki basit argümanı hem de parametre dizisindeki element tiplerinin argüman numaralarını geçirebilir. Sonrasında, bir dizi örneği otomatik yaratılır ve verilen argümanları ilişkilendirir. Örneğin;

 Console.WriteLine("x={0} y={1} z={2}", x, y, z); 

aşağıdaki koda eşdeğerdir

 string s = "x={0} y={1} z={2}";
object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine(s, args);

Önemli: Params anahtar sözcüğü kullanımının güzel bir tarafı da, opsiyonel bir kısayol oluşturmasıdır. Yani aşağıdaki şekilde bir kod yazmamızı önler:

 static object[] GetArgs() { ... }

static void Main()

{
object[ ] args = GetArgs();
object a = args[0];
object b = args[1];
object c = args[2];
Console.WriteLine("a={0} b={1} c={2}", a, b, c );
} 

Şimdi de bu methodu çağırıyorum ve compiler benim yerine bu parametreler için bir dizi oluşturuyor.

 static object[] GetArgs() { ... }

static void Main()

{
Console.WriteLine("a={0} b={1} c={2}", GetArgs() );
} 

Fakat bu günlerde, .NET içerisinde sadece çok az method bir dizi geriye döndürüyor. Bu nedenle bir çok yazılımcı, IEnumerable<T> kullanmayı esneklik amacıyla tercih ediyorlar. Bu nedenle ileride şöyle bir kod yazabilirsiniz;

 static IEnumerable<object> GetArgs { }

static void Main()
{
Console.WriteLine("x={0} y={1} z={2}", GetArgs().ToArray());
}