İlkay İlknur

just a developer...

C# 7.2 - Value Typelarda Referans Semantiği Yenilikleri

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

Visual Studio 15.5 update'inin release olması ile beraber C#'ın bir sonraki minor release'i olan 7.2 versiyonu da artık RTM oldu. Daha önceki C# 7.1 yazımda da bahsettiğim üzere minor C# versiyonları Visual Studio içerisinde default olarak aktif olmuyor ve bu özellikleri kullanabilmemiz için ufak bir ayar yapmamız gerekiyor. Bu gerekli olan ayara da C# 7.1 Yenilikleri yazımdan ulaşabilirsiniz.

C# 7.2'de de yine C# 7.1'de olduğu gibi ufak ve faydalı yenilikler var. Ancak C# 7.2 sadece ufak yeniliklerin olduğu bir release değil. C# 7.2'nin ana teması, value type'larla çalışırken onları pass-by-reference mantığı ile kullanarak value typeların sahip olduğu kopyalanma özelliklerinin önüne geçip allocationı ve kopyalanma operasyonlarını azaltmak. Bu yazımızın konusu da bu tema kapsamında gelen yenilikler. C# 7.2 ile beraber gelen diğer yeniliklere de başka bir yazıda göz atacağız.

Bu Özellikler Neden Gerekliydi ?

Value typelar, özellikle de structlar performans kritik algoritmalarda, oyunlarda, 3D modelleme vs.. gibi alanlarda oldukça fazla kullanılmakta. Bunun en önemli nedeni value typeların heap allocationa neden olmaması nedeniyle Garbage Collector'ın getirebileceği tahmin edilemeyen yüklerden ve sorunlardan olabildiğince kaçınmak. Ancak value typelar bir metoda parametre olarak geçildiklerinde eğer herhangi bir ref parametre kullanılmazsa value typeların bir kopyası yaratılıp geçilir (pass-by-value) . Örneğin, .NET Framework içerisindeki System.Windows.Media.Media3D.Matrix3D structı içerisinde 16 tane double property bulunmakta. Bu da toplamda 128 byte ediyor. Dolayısıyla siz bu structı metotlara parametre olarak geçmeye kalkarsanız bu 128 bytelık struct sürekli olarak kopyalanarak metotlara geçilecektir. Elinizde bu structlardan oldukça fazla olduğunu düşünürseniz, bir metot çağırımı bile oldukça sıkıntılı sonuçlara yol açabilir. Bundan kaçınmak için tabi ki de C# içerisinde bazı çözümler var. Ancak C# 7.2 bu özelliklere ekstra yenilikler ekleyerek özellikle structlarla çalışmayı daha kolay ve özellikle güvenilir hale getiriyor. Dolayısıyla aşağıda anlatmaya çalışacağım yenilikleri bu konseptte değerlendirmekte fayda var.

in Parametreleri

Şu ana kadar C# içerisinde bir value type'ı bir metoda referansıyla parametre olarak geçmek istediğimizde out ve ref parametrelerini kullanabiliyorduk. Böylece value typeların metot çağırılırken kopyalanmasının önüne geçebiliyoruz ve referansıyla gönderebiliyoruz. Bu seçeneklerden, out parametreyi kullandığımızda, out parametrede çağırılan metot bu değişkene mutlaka bir değer ataması yapması gerekirken, ref parametrelerde ise değişken yine referansı ile metoda gelirken metodun değişken üzerinde bir değişiklik yapması zorunlu değil.

static void Out(out int x)
{
    x = 12;
}

static void Ref(ref int x)
{
    //Bazı durumlarda değiştirilebilir.
    if (x > 10)
    {
        x = 9;
    }
}

C# 7.2 ile beraber gelen in parametreleri ise yine değişkenin referansıyla metoda gönderilmesini sağlarken çağrılan metodun bu değişkeni değiştiremeyeceğinden emin olunmasını sağlıyor. Böylece çağırılan metot in parametreyi değiştiremeyeceği için gönderdiğiniz value type'ın özellikle de struct'ın değişmediğinden emin olabilirsiniz.

Örneğin,

static void In(in int x)
{
    x = 12;
}

Yukarıdaki gibi gelen bir in parametreye bir değer ataması yapmak isterseniz derleme hatası alıyorsunuz.

in parametre kabul eden bir metodu çağırmak istediğimizde iki şekilde çağırma yapabilmemiz mümkün. Birincisi out parametrelerde olduğu gibi in parametrenin başına in keywordünü eklemek.

static void Main(string[] args)
{
    int k = 1;
    In(in k);
}

Diğeri ise in keywordünü eklemeden çağırmak. Eğer in parametre yolladığınızı kodu okuyan kişinin bilmesini istiyorsanız parametrenin başına in yazmanız faydalı olabilir. Ama kullanım şekli tamamen size kalmış.

static void Main(string[] args)
{
    int k = 1;

    In(in k);            
    In(k);
}

in parametrelerin en büyük avantajı value typelar ile elde ediliyor.Reference typelarda etkisinin pek olmadığını belirtmemizde fayda var.

ref readonly returns

in parametrelerle, parametre referansıyla ve değiştirelemez bir şekilde metoda parametre geçilirken bu özellikle de dönüş değeri olan value type referansıyla döndürülür ve bu referans üzerinden döndürülen value type değiştirilemez. Bu özelliğin faydalı olabileceği kullanım alanlarında birini kısaca anlatmaya çalışalım.

Örneğin Point isimli bir struct olduğunu düşünelim.

struct Point
{
    public int X { getset; }
    public int Y { getset; }
}

Bunun için Default değerini kapsayacak bir property koymak istersek şu şekilde bir ekleme yapmamız gerekir.

struct Point
{
    public int X { getset; }
    public int Y { getset; }
    public static Point Default => new Point();
}

Burada her Default propertysi kullanıldığında stackde yeni bir Point yaratılır. Halbuki bunun yerine ref readonly olarak döndürürsek tek bir Point yaratıp sürekli olarak bunun referansını döndürebiliriz.

struct Point
{
    public int X { getset; }
    public int Y { getset; }
    private static Point defaultp = new Point();
    public static ref readonly Point Default => ref defaultp;
}

Bu şekilde Point structını tanımladıktan sonra Point üzerinden Default propertysine aşağıdaki gibi ulaşabiliriz.

static void Main(string[] args)
{
    ref readonly var defaultp = ref Point.Default;
}

Bu şekilde bir kullanımda defaultp değişkeni içerisinde readonly referans bulunduğu için Main metot üzerinde defaultp değişkeni üzerinden bir değer ataması yapılması mümkün olmayacaktır ve compiler kodu derlemeyecektir.

static void Main(string[] args)
{
    ref readonly var defaultp = ref Point.Default;
    //ref readonly !!!
    defaultp.X = 1; //---> Mümkün değil !!!
}

ref readonly olarak dönen bir değerin kopyasını almak istersek de dönen değeri ref readonly olmayan bir değişkene atamasını yapmamız yeterli.

static void Main(string[] args)
{
    var defaultp = Point.Default;
    //Default'in yeni bir kopyası oluşturuldu. 
    defaultp.X = 1; //---> Artık mümkün :)
}

Bu özelliği isterseniz metotların dönüş değerleri için de kullanabilirsiniz. Biz örnek olarak property yaptık ancak bu özellik metotların dönüş değerleri için de kullanılabilir.

readonly Structs

C# 7.2 öncesinde readonly olarak tanımlanan bir struct variable'ı üzerinde struct içerisindeki bir instance metodu çağırmak istediğimizde derleyeceği o metodun structı değiştirip değiştirmeyeceğini bilemediği için otomatik olarak structı arka planda geçici bir değişkene atayıp o değişken üzerinden ilgili metodu çalıştırıyordu. Bunun da performans olarak bazı sıkıntıları olabiliyordu. Örnek senaryo olarak https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-the-surprising-inefficiency-of-readonly-fields/ bu yazıyı okuyabilirsiniz. Bu performans sorunları nedeniyle de çoğu zaman readonly kullanmaktan vazgeçilebiliyordu.

C# 7.2 ile beraber struct tanımlamasının başına readonly yazarak structların readonly olmasını sağlayabiliyoruz. Böylece derleyici de yukarıda belirttiğim senaryoda geçici bir değişken yaratmak durumunda kalmıyor ve performans sıkıntılarının da önüne geçilmiş olunuyor.

readonly olarak tanımladığımız structların içindeki değerlere atamalar sadece constructor içerisinde yapılabiliyor. Bunun haricinde başka bir yerde atama yapmak mümkün değil.

readonly struct Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }       
}

Şimdilik bu tema kapsamında bu yazıda bahsedeceklerimiz bu kadar. C# 7.2 ile beraber gelen ve yine tema kapsamında olan Span< T>'nin kullanımından ise başka bir yazıda bahsediyor olacağım. Bu yeniliklerin özellikle yüksek performanslı sistemlerin, oyunların optimizasyonu amacıyla yapıldığını hatırlatmakta fayda var. Biz belki kod yazarken sürekli structları veya bu yenilikleri kullanmıyor olabiliriz ancak kullandığımız frameworkler, toollar bu tipleri arka planda kullanıyorlar. Dolayısıyla bu yenilikler doğrudan olmasa bile dolaylı yoldan bizleri etkiliyor olacak. Mesela buradaki pull request içerisinde corefx içerisinde bu değişikliklerin adapte edildiğini görebilirsiniz.

Bir sonraki yazıda görüşmek üzere,



C# 7.0 - Out Variables

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

C# 7.0 ile beraber gelecek olan ufak ama oldukça kullanışlı özelliklerden biri de out variable'lar. Aslında yazının devamını okuyunca bu özellik daha önce gelmedi mi diye de düşünebilirsiniz. Çünkü bu özelliğin ilk olarak C# 6.0 ile beraber gelmesi planlanıyordu ancak Visual Studip 2015 RC versiyonuna geldiğinde C# ekibi bu feature'ı release'e kadar yetiştiremeyeceğini düşündüğü için C# 6.0'dan çıkardı. Şimdi gelelim out variable'ların kullanımına.

out keyword'ü bizim bir metottan birden fazla değer döndürmemiz gerektiği durumlarda başvurduğumuz bir keyword. Örneğin, .NET Framework içerisindeki int.TryParse metoduna bakarsak bu metodun boolean bir değer döndürdüğünü görüyoruz. Aynı zamanda bir de out parametre alıyor ve buradan da parse ettiği değeri metodu çağıran yere geri veriyor. Bir örnek yaparsak,

int result;
string str = "123123";
bool isSucceeded = int.TryParse(str, out result);

</ out keyword'ünün kullanımının en sıkıntılı yanı out parametresi olarak vereceğimiz değişkeni metodu çağırmadan önce tanımlamış olmamız gerekliliği. Çünkü genelde kod yazarken bu tanımlamaları önceden yapmayı unutuyoruz ve out parametresini gördüğümüz anda bir üst satıra geçip o değişkeni tanımlamak zorunda kalıyoruz. Bu da bizi kod yazarken yavaşlatıyor. Artık C# 7.0 ile beraber bu dikkat dağınıklığı ve kod yazarken yavaşlama durumu sona eriyor ve out parametresi olan bir metodu çağırırken inline olarak out parametreyi tanımlayabiliyoruz.

string str = "123123";
bool isSucceeded = int.TryParse(str, out int result);

Bu şekilde gördüğünüz gibi result değişkenini out parametre geçerken tanımlıyoruz ve metot çağırımından sonra da bu değişkeni kullanabiliyoruz. Hatta isterseniz out parametre tanımlarken var keywordünü de kullanbilirsiniz.

string str = "123123";
bool isSucceeded = int.TryParse(str, out var result);

out Parametreden Kurtulma

out parametre kabul eden metotları kullanırken belki de sadece metodun dönüş değerini kullanacağımız ve out parametreyi aslında sadece mecbur kaldığımız için tanımladığımız durumlar olabilir. Mesela, int.TryParse metoduna verdiğiniz string'in bir int value'nun string karşılığı olup olmadığını merak ediyor olabilirsiniz. Dolayısıyla out parametresi olarak verdiğimiz değere hiçbir yerde ihtiyacınız olmayacaktır. Bu gibi durumlar için de yine C# 7.0 out variables syntaxını kullanarak out variable tanımlamaktan kurtulabiliriz. Bunun için out variable'ın adını yazmak yerine _ koyarsak out parametre tanımlamaktan kurtulmuş oluyoruz. Arka plandaki gerekli işlemleri derleyici kendisi hallediyor.

string str = "123123";
if (int.TryParse(str, out var _))
{

}

C# 7.0 içerisindeki belki de en ufak özelliklerden biri out variable'lar. Ancak çözdüğü probleme baktığımızda bu versiyonda en çok kullanılacak olan özelliklerden biri olmaya aday.

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



C# 7.0 - Tuples

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

C# 7.0 ile beraber gelecek olan önemli özelliklerden biri de Tuple'lar. Tuple tiplerine aslında çokta yabancı değiliz. Tuple tipiyle ilk olarak .NET Framework 4.0 ile tanışmıştık. Hani şu içerisinde Item1, Item2, Item3 diye propertyler olan tipler 😃 Çoğu zaman aslında kullanmak istediğimiz ama bu property isimlerinden dolayı kullanma konusunda içimizin rahat olmadığı tipler 😃

Tupleların en önemli kullanım alanları aslında bir metottan birden fazla değer döndürmek zorunda kaldığımız durumlar. C# içerisinde aslında bir metottan birden fazla değer döndürmek istediğimizde out parametrelerini kullanabiliyoruz.

private void Foo(int param1, out int intReturnParam, out string stringReturnParam)
{

}

Ancak out parametreler maalesef async metotlarda kullanılamıyor. Dolayısıyla bu durumda Tuple tipini kullanmak durumunda kalabiliyoruz.

private async Task<Tuple<intstring>> FooAsync(int param1)
{

}

private async void Barrier()
{
    var result = await FooAsync(1);
    Debug.WriteLine($"{result.Item1} : {result.Item2}");
}

Bu durumda da metodu çağıran veya kullanan yazılımcılar aslında bu async metottan ne döndüğü konusunda hiçbir şekilde bilgi sahibi olamıyorlar. Item1, Item2 fieldlarını ne işe yaradığını, hangisinde hangi bilgi olduğunu iyi bir şekilde dökümante etmeniz gerekiyor. Ayrıca mevcut Tuple tipi bir class olduğu için heap allocationa neden oluyor.

Tüm bu nedenlerden dolayı aslında gerçek anlamda Tuple desteği C# 7.0 ile beraber geliyor. Peki C# 7.0'da Tuple'lar nasıl olacak. Gelelim bu kısma.

Not: İlk olarak bu özelliği kullanmadan önce projemize System.ValueTuple nuget paketini eklememiz gerekiyor.

Metotlarda birden fazla değer döndürmek istediğimizde döndüreceğimiz alanların tiplerini ve isimlerini parantez içerisinde yazmamız gerekecek.

static (int count, string value) Foo()
{

}

static Task<(int count, string value)> FooAsync()
{

}

Yukarıda gördüğünüz gibi Foo metodunun dönüş değeri bir tuple ve bu tuple'ın içerisinde count ve value diye 2 tane alan bulunuyor. Ayrıca Task döndüren asenkron metotların da dönüş tipleri gördüğünüz gibi tuple olabiliyor. Bu şekilde tuple dönüş tiplerini tanımladıktan sonra peki metot içerisinde bir tuple nasıl tanımlıyoruz kısmına bakalım.

static(int count, string value) Foo()
{
    var retVal = (count: 1, value: "Foo");
    return retVal;
}

Parantez içerisinde sadece alanın adını ve değerini yazdığınızda da compiler arka planda aynı anonymous objectlerde olduğu gibi alanın tipini kendisi buluyor ve ona uygun tuple tipini yaratıyor. Ayrıca yine tipin compiler tarafından bilindiği durumlarda da tuple'lardaki field adlarının pekte önemi olmuyor. Compiler arka planda ilgili çevrimi kendisi yapıyor. Önemli olan tuple tipindeki alanların tiplerinin uyuşması.

static (int count, string value) Foo()
{
    var retVal = (c: 1, v: "Foo");
    return retVal;
}

Tuple Deconstruction

Bir metottan veya herhangi bir yerden bir tuple döndüğünde o tuple içerisindeki değerleri ayrıştırmak ve metodun devamında ayrıştırılmış halini kullanmak önemli. Bu yüzden tuple içerisindeki alanları ayrıştırıp içerisindeki değerleri değişkenlere atamak için de kolay bir syntax geliyor C# 7.0 ile.

static void Main(string[] args)
{
    var (count, value) = Foo();
}

static (int count, string value) Foo()
{
    var retVal = (c: 1, v: "Foo");
    return retVal;
}

Main metodunda göründüğü gibi Foo metodundan dönen tuple tipi içerisindeki alanları count ve value ismindeki local değişkenlere atamasını yapabiliyoruz. Böylece aslında tuple tipleri tamamen görünmez bir şekilde kalabiliyorlar. Yani siz bir metottan tuple döndüğünü biliyorsanız, bu tuple içerisindeki alanları hemen hızlıca lokal değişkenlere alıp kodunuzu temiz tutabilirsiniz.

Bazen de metotlardan dönen tuple içerisindeki sadece belirli alanlar işinize yarayabilir. Bu durumda tuple içerisindeki tüm alanları deconstruct etmek yerine istediğiniz değerleri deconstruct edip değişkenlere atayabilirsiniz. Bunun için yukarıda gösterdiğim syntax'ı kullanarak değişken ismi yerine _ koymanız yeterli.

static void Main(string[] args)
{
    var (count, _) = Foo();
}

static (int count, string value) Foo()
{
    var retVal = (c: 1, v: "Foo");
    return retVal;
}

C# 7.0 ile beraber gelen tuple'ların Framework içerisinde bulanan Tuple'lardan bazı farkları var. Bunlardan ilki C# 7.0 tuple'larının struct olması. Böylece bu tupleların yaratılmaları daha az maliyetli. Ayrıca C# 7.0 tuple'ları mutable. Yani bir tuple yarattıktan sonra fieldın değerini değiştirebilirsiniz. Ancak .NET Framework içerisindeki Tuple tipleri immutable. Yani yarattıktan sonra fieldın değerini sadece okuyabilirsiniz, değiştiremezsiniz.

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



C# 7.0 - Pattern Matching

Şu ana kadar C# 7.0 ile beraber gelmesi planlanan pek çok özelliği inceledik. Bu blog postu da saymazsak incelemediğimiz sadece 3 özellik kalıyor. BU özellikler pattern matching, out var ve tuples.

Bu blog postta ise pattern matching özelliğine bakacağız. C# 7.0 ile beraber gelecek en büyük özelliklerden biri olan pattern matching ile ilgili çektiğim videoyu aşağıdan izleyebilirsiniz.

Video Linki : https://www.youtube.com/watch?v=HzSh0PFBfoQ

Pattern Matching Kod Örnekleri: https://github.com/ilkayilknur/csharp-7-new-features/tree/master/PatternMatching

Görüşmek Üzere



C# 7.0 Kod Örnekleri

C# 6.0 zamanlarında özellikleri anlatan kod örneklerini Github üzerinden paylaşmıştım. C# 7.0 versiyonu da artık yavaş yavaş belirginleşmeye ve release olmaya doğru giderken benzer tarzda örnekleri C# 7.0 içinde koymaya karar verdim.

İlgili örneklere https://github.com/ilkayilknur/csharp-7-new-features adresinden ulaşabilirsiniz. Hatta ekleyebileceğiniz özel kullanım durumları da varsa pull request gönderebilirsiniz.

Görüşmek Üzere



Visual Studio VNext - Visual Studio 15 Preview Yenilikleri

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

Bu seneki Build konferansının en önemli duyurularından biri de Visual Studio'nun bir sonraki major versiyonu olacak olan Visual Studio 15 Preview'dı. Daha ilk preview olmasına rağmen içerisinde güzel yenilikleri barındıran Visual Studio 15'e bu yazımızda kısaca bir gözatacağız.

Visual Studio 15 Preview adından da anlaşılacağı gibi henüz preview :). Dolayısıyla bilgisayarınıza yüklediğinizde eğer bir sorunla karşılaşırsanız bu sorunla ilgili Microsoft size destek vermeyecektir. Bu nedenle tavsiyem bilgisayarınızda yeni bir sanal makinada veya Azure üzerinde bir sanal makinada bu yenilikleri test etmeniz.

Yeni Installer

Bu yazıyı okuyorsanız tahmin ediyorum ki %90 ihtimalle hayatınızda en az bir kere makinanıza Visual Studio kurulumu yapmışsınızdır. Önce ufak bir setup dosyası download edilir sonra o setup dosyası arka planda 1-2 GB büyüklüğünde olan kurulum dosyalarını download eder ve sonrasında da 45dk-1 saate yakın Visual Studio'nun kurulmasını bekleriz. Kurulum zamanı setup sırasında nelerin kurulmasını istediğinize göre de değişir. Bazen developerlar yeniden kurulumla uğraşmamak için belki ileride lazım olur diye tüm opsiyonları seçerler. Bazen de default seçimle ilerlerler. Visual Studio ekibindeki kullanım datalarına bakarsak default seçenekle ilerleyen developerların %70'i default içerisindeki tüm functionalityleri ve tüm app tiplerini bile kullanmıyor.

Bu nedenlerden dolayı Visual Studio VNext'in en büyük temalarından biri bu kurulum aşamasını kolaylaştırmak ve hızlandırmak olacak. Hem setup dosyasının boyutunu ufaltma, hem kurulumu hızlandırma hem de Visual Studio'yu uygulama tipleri bazında modüler hale getirme şimdilik düşünülen yeniliklerden. Şimdi gelelim ilk preview'daki installer yeniliklerine.

http://aka.ms/vsnext adresine gidip sağdaki seçenekten yeni installerı download edip installer yeniliklerini test edebilirsiniz. Soldaki installer ise bildiğimiz klasik Visual Studio installerı.

Installerı download edip çalıştırdığımızda önceki versiyonların installerlarına nazaran farklı bir ekranla karşılaşıyoruz.

Ekrandan da görüleceği gibi artık Visual Studio çeşitli uygulama tiplerine uygun paketlerle geliyor. Yani eğer sadece .NET development yapacaksanız .NET Development paketini kurup hızlı bir şekilde kurulumu tamamlayabilirsiniz. Eğer sadece editör olarak kullanacağım diyorsanız core editörü seçip 300 MB'lık bir download yapıp Visual Studio'yu kurabiliyorsunuz. Şu anda sadece yukarıda resimde gördüğünüz kadar paket mevcut. Ancak zaman içerisinde Universal App paketi, Cloud App paketi gibi daha pek çok farklı paket installera eklenecektir.

Paket bazılı kurulumla beraber kurulum süreleri de gözle görülür bir şekilde düşüyor. Daha önce Visual Studio kurulumuyla 1-1.5 saatimizi harcarken artık çok daha kısa bir sürede kurulumu tamamlayabiliyoruz. Örneğin sadece core editör paketini yüklemek isterseniz max 10 dk içerisinde kurulum tamamlanıyor.(Paket downloadu yapıldığı için internet hızına göre değişiklik gösterebilir.)

Hızlandırılmış İlk Açılım

1-1.5 saat bekleyip Visual Studio kurulumunu tamamladıktan sonra ilk açtığımızda kullanacağımız dile göre seçim yaparız ve bir süre Visual Studio'nun ilk kullanıma hazırlanmasını bekleriz. Her ne kadar uyarıda bir kaç dakika sürebilir dese de bu süreç oldukça uzun sürebilmekte. Benim de şahsen Visual Studio kurulumunda en sinir olduğum şeylerden biridir bu :)

Visual Studio 15 ile beraber artık bu süre de oldukça kısalıyor ve 1 dakikayı bile bulmadan Visual Studio ilk kez bile olsa hızlıca açılıyor. Şahsen en sevdiğim özelliklerden biride bu Visual Studio 15 içerisindeki :D

Klasör Bazlı Çalışabilme

Yeni Visual Studio'nun en önemli özelliklerinden biri de klasör bazlı çalışabilme. Bundan önce Visual Studio'da bir projeyi açmak istediğimizde o projenin illa ki bir solution dosyası olması gerekiyordu. Ancak yeni Visual Studio ile beraber bu zorunluluk ortadan kalktı. Artık solution dosyası olmadan da doğrudan klasör bazlı olarak projeyi açıp çalışabiliyoruz. Böylece python, javascript gibi solution dosyası gerektirmeyen proje tipleriyle Visual Studio içerisinde çalışmak daha da kolay.

Solution Explorer içerisinde klasör görünümü

Code Style Rules

Roslyn projesiyle beraber custom analyzer yazabilmeye başlamamızla en çok sorulan sorulardan biri de proje içerisindeki code standartlarını korumak için bir analyzer yapabilmenin mümkün olup olmadıydı. Bununla ilgili 1-2 proje olsa da tam anlamıyla çözülemeyen bir sorundu bu. Artık Visual Studio 15'le beraber kodlama standartlarına uygun kurallar tanımlayabiliyoruz ve bu kuralların kontrolü projenin derleme aşamasında yapılıyor. Eğer kurala uygun olmayan kullanımlar varsa bunların fixleri için de Visual Studio size ilgili code fixi otomatik olarak sunuyor.

Code style kuralları şu anda Tools => Options ekranı içerisinde Text Editor => C# altında yer alıyor. Bu ekran içerisinde pek çok kural ayarlamak mümkün. Örneğin, projeler içerisinde this ve var keywordlerinin kullanımlarını belirleyebiliyorsunuz. Developerlar var keywordünü hiç kullanmamalı yada tipin belirgin olduğu durumlarda var keywordu kullanılabilir gibi kuralları ayarlayabiliyorsunuz.

Eğer isimlendirmelerle ilgili kurallar belirlemek isterseniz bu kuralları da yine aynı ekran üzerinden ayarlayabiliyorsunuz. Örneğin projede metot isimlendirmelerinde pascal casing kullanılsın diye ayarlarsanız Visual Studio derleme esnasında sizi isimlendirme kurallarına uymadığınız konusunda uyarıyor ve bunu düzeltmeniz için size code fix sunuyor.

Kuralları belirledikten sonra Visual Studio belirlediğimiz kurallara göre build esnasında ilgili uyarıları yapıyor.

"Show Potential Fixes"'a basarsak

Code style rule ekranı içerisinde yapılabilecek pek çok farklı ayar bulunmakta. Bununla ilgili daha detaylı bir şekilde başka bir blog yazısı yazmayı planlıyorum.

Diğer Özellikler

Build'deki sessionları izlerseniz Visual Studio 15 içerisinde yukarıda bahsettiklerime ek olarak gösterilen pek çok farklı özellik olduğunu göreceksiniz. Intellisense substring matching, intellisense filtering, "how do I" (en heyecanlanmadığım özellik :( ), code fix ile proje veya nuget paket referansı ekleme (en efsane özellik :) ) gibi özelliklerin hepsi private buildler üzerinden gösterildi. Bunun anlamı bu özellikle şu an için kullanıcılar tarafından test edilmek için hazır değiller. Tahmin ediyorum ki ilerleyen preview sürümlerinde bu özellikler de yavaş yavaş Visual Studio 15 içerisine eklenecektir.

Build'de Visual Studio 15 ile ilgili oturumlara aşağıda ulaşabilirsiniz.

Visual Studio 15 Preview yenilikleri şimdilik bu kadar. Yeni preview sürümleri çıktıkça içerisinde bulunan yeni özellikleri yine blogdan paylaşıyor olacağım.

Görüşmek üzere...



Roslyn - CSharpSyntaxWalker İle Syntax Tree Üzerinde Gezinme

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

Bir önceki roslyn makalemizde syntax tree yapısından ve syntax tree içerisinde bulunan yapılardan bahsetmiştik. Ayrıca syntax tree içerisinde istediğimiz yapıdaki elementleri bulmak için LINQ kullanmıştık. Bu yazıda ise syntax tree içerisinde gezinmenin farklı bir yolunu inceleyeceğiz. İlk olarak projemize yine Microsoft.CodeAnalysis nuget paketini ekliyoruz.

Install-Package Microsoft.CodeAnalysis 

Genelde syntax treeler ile çalışırken amacımız belirli bir tipte olan elementleri bulup bunlar üzerinde işlemler yapmak oluyor. Örneğin, tüm class tanımlamalarını bulup kontrol etmek veya değişken tanımlamalarını bulup kontrol etmek gibi... Bu nedenle aslında çoğunlukla ilk yaptığımız şey syntax tree içerisinde bulmayı amaçladığımız tanımlamalara gitmek. SyntaxWalker yapısı da otomatik olarak syntax tree üzerinde kolayca gezinmemizi ve istediğimiz tanımlamalara hızlıca ulaşmamızı sağlayan bir yapı. C# syntax tree'leri için bunun özelleşmiş hali de CSharpSyntaxWalker tipi. CSharpSyntaxWalker tipinin yapısına bakarsak bu tip abstract bir tip. Bu nedenle kendi SyntaxWalker tipimizi yazıp bu tipten türeteceğiz.

public class MethodWalker : CSharpSyntaxWalker
{

}

Diyelim ki çalıştığınız şirkette kullandığınız bir kodlama standardı var ve bu standarda göre yazdığınız metotların isimleri büyük harfle başlamalı. Bu nedenle syntax walker ile syntax tree içerisinde gezip metot tanımlamalarını bulup bu metotların isimlerini kontrol etmeliyiz.

Yukarıda gördüğünüz üzere CSharpSyntaxWalker içerisinde override edilebilecek çok sayıda metot var. Bu metotları override ettiğinizde syntax tree içerisinde metodun isminde yazan elementlere denk gelindiğinde override ettiğiniz metot çağırılıyor. Böylece sizde istediğiniz işlemleri burada yapabiliyorsunuz. En basitinden eğer tüm nodeları veya tokenları gezmek isterseniz Visit ve VisitToken metotlarını override edebilirsiniz. Böylece hızlı bir şekilde tree üzerinde istediğiniz elemanlara erişebilirsiniz.

public class MethodWalker : CSharpSyntaxWalker
{
    public override void VisitToken(SyntaxToken token)
    {
        base.VisitToken(token);
    }

    public override void Visit(SyntaxNode node)
    {
        base.Visit(node);
    }
}

Ancak daha spesifik tanımlamalara erişmekse amacınız daha özelleşmiş metotlar işinizi daha da kolaylaştırabilir. Bizim örneğinizde aslında biz sadece metot tanımlamalarına odaklanmak istiyoruz. Bu nedenle metot tanımlamalarına erişmek bizim için yeterli. Bu nedenle doğrudan VisitMethodDeclaration metodunu override ederek ilerliyoruz.

public class MethodWalker : CSharpSyntaxWalker
{
    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        base.VisitMethodDeclaration(node);
    }
}

Şimdi sıra geldi ilgili kontrolü yapma aşamasına. VisitMethodDeclaration metodunu override ettiğimizde bize artık MethodDeclarationSyntax node'u parametre olarak geliyor. Artık yapmamız gereken metot adının ilk harfini kontrol edip bir eğer illegal bir kullanım varsa ismini collection içerisine eklemek.

public class MethodWalker : CSharpSyntaxWalker
{
    public List<string> illegalMethodNames = new List<string>();
    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        if (char.IsLower(node.Identifier.ValueText, 0))
        {
            illegalMethodNames.Add(node.Identifier.ValueText);
        }
    }
}

Gördüğünüz gibi bu kadar basit. Şimdi sıra geldi bu yazdığımız syntax walker aracılığıyla kod üzerinde gezinmeye.

class Program
{
    static void Main(string[] args)
    {
        var tree = CSharpSyntaxTree.ParseText(@"class Foo
                {
                    void Bar()
                    {
                    }

                    void bar1()
                    {
                        //Illegal
                    }

                    void Bar2()
                    {
                    }

                    void m()
                    {
                        //Illegal
                    }

                    class InnerFoo
                    {
                        void innerM()
                        {
                            //Illegal
                        }
                    }
                }"
);

        MethodWalker methodWalker = new MethodWalker();
        methodWalker.Visit(tree.GetRoot());

        methodWalker.illegalMethodNames.ForEach(t => Console.WriteLine(t));
    }
}

Gördüğünüz gibi syntax tree yarattıktan sonra syntax walkerın visit metodunu syntax tree'nin rootunu göndererek çağırıyoruz ve ağaç üzerinde gezinmeye başlıyoruz. Uygulamayı çalıştırırsak çıktı olarak küçük harfle başlayan metotların isimlerinin ekrana yazıldığını görebiliriz.

Gördüğünüz üzere syntax tree üzerinde gezinmek roslyn API'ları sayesinde oldukça kolay. Siz de kod içerisinde belirli analizleri yapmak için hızlı bir şekilde ve oldukça az kod yazıp ilgili kontrolleri yapabilirsiniz. Peki analizleri yaptık ve hataları kısımları bulduk. Bunları nasıl temizleyeceğiz ? Örneğin küçük harfle başlayan metotların isimlerini otomatik olarak değiştirsek nasıl olur ? Bu da bir sonraki roslyn makalesinin konusu olacak :)

Bir sonraki makalede görüşmek üzere...



Roslyn Syntax Tree API'larına Giriş

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

Daha önce .NET Compiler Platform (Roslyn) ile ilgili yazdığımız tüm yazılarda ve videolarda Roslyn projesinin derleyicileri bir kara kutu olmaktan çıkardığından ve artık bizim de derleyicilerin kullandığı yapıları kullanıp elimizdeki kodu analiz edebileceğimizden çokca bahsetmiştik.

Bugün bu konuya syntax tree API'ları ile giriş yapacağız. Yazdığımız kodlar derleme işlemi sırasında ilk olarak derleyici tarafından parse edilirler ve bu işlemin sonucunda bir syntax tree oluşturulur. Bu syntax tree içerisinde yazmış olduğumuz kod içerisindeki herşey hiyerarşik bir şekilde tutulur.

Örneğin, aşağıdaki gibi son derece basit bir kodumuz olduğunu düşünelim.

class C
{
    public void M()
    { }
}

Bu kod derleyici tarafından derlendiğinde aşağıdaki gibi bir syntax tree oluşturulur.

Gördüğümüz gibi yazmış olduğumuz kod içerisinde bulunan her bir tanımlama, boşluk, keyword vb... tüm bileşenler syntax tree içerisinde eksiksiz tutuluyor. Syntax tree'ler derleme aşamasının en önemli yapılarından biri. Çünkü kod içerisindeki tüm bileşenler syntax tree içerisinde düzenleniyor ve kategorilere ayrılıyor. Derleme işlemi sırasında parserdan sonraki adımlarda da bu aşamada üretilen syntax tree kullanılıyor.

Derleyici tarafından yaratılan syntax tree içerisinde 3 farklı tipte eleman bulunuyor.

  • Syntax Node
  • Syntax Token
  • Syntax Trivia

Syntax Node'lar kod içerisindeki tanımlamaları,ifadeleri, koşul ifadeleri vb... dil içerisinde temel yapıları temsil ederler. Örneğin class tanımlaması en tepede bir syntax node ile ifade edilir. Roslyn içerisinde her yapıya özel SyntaxNode tipinden türetilmiş özel SyntaxNode sınıfları vardır. Yukarıdaki syntax tree resminde mavi olan nodelar birer syntax node'dur.

Syntax Token'lar yine dil içerisinde bulunan en ufak bileşenleri temsil eden yapılardır. Syntax tokenların tree içerisinde altında hiçbir zaman başka tokenlar veya syntax node'lar bulunmaz. Roslyn içerisinde SyntaxToken'lar için tek bir tip bulunurken bu tip içerisindeki Kind propertysi ile token tipleri birbirinden ayrıştırılabilir. Yukarıdaki syntax tree resmindeki yeşil renkli nodelar syntax tokendır.

Syntax Trivia'lar da kod içerisinde bulunan ancak derleme işlemi için çokta büyük anlam ifade etmeyen bileşenler için kullanılır. Bu bileşenler kod içerisindeki yorumlar, boşluklar gibi yapılardır. Syntax trivia'lar syntax tokenlara bağlıdırlar. Her bir syntax token'ın LeadingTrivia ve TrailingTrivia collectionları vardır. Bu collectionlar ile token'a bağlı olan trivia'lara erişilebilir.

Bu kadar teori şimdilik yeterli :) Şimdi hemen bir console uygulaması açalım ve bu API'ları nasıl kullanacağımıza bakalım.

Projemizi yarattıktan sonra yapmamız gereken ilk şey Microsoft.CodeAnalysis nuget paketini yüklemek.

Install-Package Microsoft.CodeAnalysis 

Bu işlemi de gerçekleştirdikten sonra artık hazırız. Varolan bir kodun syntax tree'sini çıkarabilmek için Microsoft.CodeAnalysis.CSharp namespace'i içerisinde CSharpSyntaxTree tipinin ParseText metodunu kullanacağız. Bu metot string olarak kendisine verilen bir kodun syntax treesini verir.

class Program
{
    static void Main()
    {
        var tree = CSharpSyntaxTree.ParseText(@"class C
        {
            public void M()
            { }
        }"
);
    }
}

ParseText metodunu çağırdıktan sonra artık elimizde bir syntax tree var. Bu syntax tree üzerinde istediğimiz elemanlara ulaşabiliriz ve ağaç üzerinde gezebiliriz. İlk olarak yaratılan veri yapısı bir ağaç olduğu için ağacın root elemanına ulaşalım.

class Program
{
    static void Main(string[] args)
    {
        var tree = CSharpSyntaxTree.ParseText(@"class C
        {
            public void M()
            { }
        }"
);

        var root = tree.GetRoot();
    }
}   

Ağacın rootuna ulaştıktan sonra artık ağaç içerisindeki her bir elamana da rahatlıkla ulaşabiliriz. Örneğin basit bir LINQ sorgusuyla tüm metot tanımlamalarına ulaşmamız mümkün.

class Program
{
    static void Main(string[] args)
    {
        var tree = CSharpSyntaxTree.ParseText(@"class C
        {
            public void M()
            { }
        }"
);

        var root = tree.GetRoot();
        var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
        foreach (var method in methods)
        {
            Console.WriteLine(method.ToString());
        }
    }
}

Bu kod çalıştığında parse ettiğimiz kod içerisinde tek metot olduğu için ve o metot da M metodu olduğu için M metodu doğrudan console'a yazılacak.

Olayı biraz daha karmaşıklaştıralım ve parametre kabul eden metotların listesini çıkaralım.

class Program
{
    static void Main(string[] args)
    {
        var tree = CSharpSyntaxTree.ParseText(@"class C
        {
            public void M()
            { }
            public void M2(int a)
            { }
            public void M3(int a,string b)
            { }
        }"
);

        var root = tree.GetRoot();
        var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>().Where(t => t.ParameterList.Parameters.Any());
        foreach (var method in methods)
        {
            Console.WriteLine(method.ToString());
        }
    }
}

Parse ettiğimiz koda baktığımızda yazdığımız kodun çıktısının M2 ve M3 metotları olması lazım. Hemen kodu çalıştırıp sonucunu görelim.

Şimdi eminim aklınıza takılan şöyle bir sorun var. Bu sorguları bu şekilde iyi güzel yazıyoruzda metot tanımlamalarının MethodDeclarationSyntax tipinde olduğunu veya parametrelerin bu tip içerisindeki ParameterList.Parameters collectionında tutulduğunu nasıl bileceğiz ? Bu konu tabi sadece bizim değil aynı zaman da Roslyn'i ve Roslyn-Visual Studio entagrasyonunu yazan ekibin de bir sorunuydu zamanında. Bu nedenle geliştirdikleri bir araçla bu sorunu çözdüler. Şimdi bu araç artık Visual Studio içerisinde extension olarak sunuluyor.

Syntax Visualizer dediğimiz pencere yardımıyla istediğimiz kodun syntax treesini herhangi bir kod çalıştırmadan görebiliriz ve her bir elemanın tüm propertylerine de ulaşabiliriz. Bu extensionı yüklemek için .NET Compiler Platform SDK'ini yüklememiz gerekiyor. Bu extension aynı zamanda Roslyn API'larıyla yaptığımız kod analizlerini Visual Studio extensionı olarak yayınlamamız sağlayan templateları da sağlıyor. Extensionı kurmak için buradan VS Gallery'e gidebilir veya Visual Studio içerisiden Tools => Extensions and Updates menüsü üzerinde .NET Compiler Platform SDK'ini yükleyebilirsiniz.

Extension'ı yükledikten sonra View => Other Windows => Syntax Visualizer adımlarıyla Syntax Visualizer'ı açabilirsiniz.

Parse ettiğimiz kodu da Visual Studio içerisine kopyalarsak aşağıda gördüğünüz gibi hızlı bir şekilde elemanlara ulaşabilir ve propertylerini hızlı bir şekilde kontrol edebiliriz.

Bu yazımızda syntax tree API'larını kısa bir giriş yaptık ve LINQ ile syntax tree üzerinde elemanlara nasıl erişilebiliriz konusunu inceledik. Bir sonraki yazıda syntax tree üzerinde dolaşmanın farklı bir yolunu inceleyeceğiz.

Görüşmek üzere...



Roslyn Scripting APIs

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

Bir önceki yazımızda Visual Studio 2015 Update 1 ile gelen Interactive Window'u incelemiştik. Interactive Window ile C# kodlarını Visual Studio içerisinde hızlı bir şekilde çalıştırıp anında sonuçlarını görebiliyoruz. Nasıl browserda console içerisinde script çalıştırıp sonucunu anında görebiliyorsak aynısını artık C# için de yapabiliyoruz. Peki bu nasıl mümkün oluyor ?

Aslında tüm sihir Roslyn'in scripting API'larında. Yani Interactive Window içerisinde bir kod yazdığınızda bu kod scripting API'ları aracılığıyla Roslyn'e gönderiliyor ve arkada derlenip, çalıştırılıp sonucu bize geri veriliyor. Şimdi isterseniz gelin bu scripting API'larına biraz gözatalım.

Scripting API'larına ulaşabilmemiz için projemize ilk olarak Microsoft.CodeAnalysis.Scripting nuget paketini yüklememiz gerekiyor.

PM> Install-Package Microsoft.CodeAnalysis.Scripting 

Nuget paketini yüklediğimiz proje eğer .NET Framework 4.6'yı target etmiyorsa aşağıdaki hatayı alıyoruz. Bu hatayı almamak için projemizi .NET Framework 4.6'ya yükseltmemiz gerekiyor.

Could not install package 'System.Runtime 4.0.20'. You are trying to install this package into a project that targets '.NETFramework,Version=v4.5.2', but the package does not contain any assembly references or content files that are compatible with that framework

Nuget paketini başarılı bir şekilde yükledikten sonra artık scripting API'larını test etmeye hazırız. Genel olarak C# kodlarını çalıştırmak için kullanacağımız tipler Microsoft.CodeAnalysis.CSharp.Scripting namespace'i içerisinde bulunuyor.

İlk olarak en basit olandan başlıyoruz. CSharpScript.EvaluateScript metodu bizim basit C# ifadelerini çalıştırıp sonuçlarını almamızı sağlayan metot.

class Program
{
    static void Main(string[] args)
    {
        RunAsync().Wait();
    }

    static async Task RunAsync()
    {
        var result = await CSharpScript.EvaluateAsync("1+1");
        var result2 = await CSharpScript.EvaluateAsync<int>("1+1");
        Console.WriteLine($"result:{result}, result2:{result2}");
    }
}

Eğer EvaluateAsync'in generic olmayan metodunu kullanırsak bize sonuç object tipinden dönüyor. Ancak generic metodu kullanırsak strongly-typed olarak script'in sonucunu alabiliyoruz.

class Program
{
    static void Main(string[] args)
    {
        RunAsync().Wait();
    }

    static async Task RunAsync()
    {
        var result = await CSharpScript.EvaluateAsync<int>("int x=10;int y=12; int z=x+y; z");
        Console.WriteLine($"result:{result}");
    }
}

Yukarıda yazdığımız koda tekrar bakmamızda fayda var. "int x=10;int y=12; int z=x+y; z" aslında derlenebilir bir C# kodu değil. Ancak scripting API'larında bir değişkenin o anki değerini alabilmek için doğrudan adını yazdığımızda değerini alabiliyoruz. Aynı diğer scripting ortamlarında olduğu gibi.

EvaluateAsync metodu opsiyonel ikinci parametre olarak bizden ScriptOptions tipinde bir object bekliyor. Bu parametre ile istediğimiz namespace'i veya kütüphaneyi referans olarak ekleyebiliyoruz. Böylece EvaluateAsync içerisinde çalıştıracağımız kodlarda eklediğimiz kütüphanelerden ve namespacelerden tipleri kullanabiliyoruz. Eğer bu namespaceleri veya kütüphaneleri eklemezsek runtimeda scriptin derlenmesi esnasında compilation error alırız.

Örneğin C# 6.0 ile beraber static tiplerin isimlerini using ile eklediğimizde kod içerisinde artık doğrudan tipin ismini kullanmadan metodun adıyla çağrım yapabiliyorduk. Math sınıfı içerisindeki Tan metodunu çağırdığımız aşağıdaki kodu düşünelim.

static void Main(string[] args)
{
    RunAsync().Wait();
}

static async Task RunAsync()
{
    try
    {
        var result = await CSharpScript.EvaluateAsync<int>("Tan(20);");
        Console.WriteLine($"result:{result}");
    }
    catch (CompilationErrorException ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

Bu kodu çalıştırdığımızda compilation error alırız. Çünkü using ifadesi ile Math tipini script içerisinde referans almadık.

Eğer aşağıdaki gibi System.Math namespace'ini script içerisine referans olarak eklersek Tan(20) kodu başarılı olarak çalışacak ve sonucunu alabileceğiz.

static void Main(string[] args)
{
    RunAsync().Wait();
}

static async Task RunAsync()
{
    try
    {
        var result = await CSharpScript.EvaluateAsync<double>("Tan(90);"ScriptOptions.Default.WithImports("System.Math"));
        Console.WriteLine($"result:{result}");
    }
    catch (CompilationErrorException ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

Şimdiye kadar yaptığımız örneklerden de gördüğünüz üzere EvaluateAsync metodu context bağımsız olarak çalışıyor. Yani bir kod veriyorsunuz, derleyip, kodu çalıştırıyor ancak sonrasında içerisindeki tüm değişkenler ve değerleri kaybolup gidiyor. Peki ya aynı interactive window senaryosunda olduğu gibi çalıştıracağımız kodu dışarıdan alıyorsak ve bu kodu da çalıştırdığımız contexti korumak istiyorsak ne yapacağız ? İşte tam burada devreye EvaluateAsync metodunun biraz daha gelişmiş versiyonu olan RunAsync metodu devreye giriyor.

RunAsync metodu EvaluateAsync metodunun aksine bize ScriptState tipinde bir object döndürüyor. Bu object de kodu çalıştırdığımız contexti içerisinde barındırıyor.

static void Main(string[] args)
{
    RunAsync().Wait();
}

static async Task RunAsync()
{
    var state = await CSharpScript.RunAsync<int>("1+2");
}

ScriptState içerisindeki property ve metotlara kısaca bakarsak.

  • ContinueWithAsync metotları tahmin edeceğiniz üzere mevcut context üzerinden yeni kodlar çalıştırmamızı sağlar.
  • GetVariable metodu script içerisinde o anda tanımlı olan belirli bir değişkenle ilgili bilgileri alabilmemizi sağlar.
  • ReturnValue ise çalıştırdığımız kodun geri dönüş değerini içerir. EvaluteAsync metodundan doğrudan dönen değer burada ReturnValue propertysinde bulunuyor.
  • Script propertysi son çalıştırdığımız script ile ilgili bilgileri saklar.
  • Variables propertysi ise script içerisinde o anda tanımlı olan tüm değişkenlerler ile ilgili bilgileri içerisinde barındırır.

Şimdi ContinueWithAsync ile ufak bir örnek yapalım.

static void Main(string[] args)
{
    RunAsync().Wait();
}

static async Task RunAsync()
{
    var state = await CSharpScript.RunAsync<int>("1+2");
    Console.WriteLine(state.ReturnValue);
    var state2 = await state.ContinueWithAsync("int i=1;");
    var state3 = await state2.ContinueWithAsync<int>("i+5");
    Console.WriteLine(state3.ReturnValue);
}

Yukarıda görüldüğü gibi öncelikle basit bir toplama işlemi çalıştırdık. Sonrasında aynı context içerisinden devam edip bir değişken tanımladık. Bir sonraki adımda ise bu değişkeni kullanarak bir toplama işlemi daha yaptık. Burada en çok dikkat çekmek istediğim nokta buradaki işlemlerden dönen tiplerin immutable olması. Yani her yeni script çalıştırışımızda bize yeni bir ScriptState nesnesi geliyor. Aslında baktığımızda bu state nesnelerini uygun bir şekilde saklarsak ihtiyacımıza göre ilgili kodları hiç çalıştırmamış gibi bir önceki state üzerinden devam etme imkanına da sahip olabiliriz.

Yukarıdaki kodun çıktısını aşağıda görebilirsiniz.

Yukarıda ScriptState tipi içerisinde script içerisinde tanımladığımız değişkenlerle ilgili bilgileri içerisinde saklar demiştik. Şimdi bununla ilgili de ufak bir örnek yapalım.

static void Main(string[] args)
{
    RunAsync().Wait();
}

static async Task RunAsync()
{
    var state = await CSharpScript.RunAsync("int x=1;");
    PrintVariables(state);
    var state2 = await state.ContinueWithAsync("x=4;");
    PrintVariables(state2);
    var state3 = await state2.ContinueWithAsync("x=10;");
    PrintVariables(state3);
}

private static void PrintVariables(ScriptState state)
{
    foreach (var variable in state.Variables)
    {
        Console.WriteLine($"Variable Name: {variable.Name},Type: {variable.Type.Name}, Value: {variable.Value}");
    }
}

ScriptState nesnesi o anda script içerisinde tanımlı olan değişkenlerle ilgili isim,tip ve o anki değeri gibi bilgileri içerisinde barındırıyor. Dolayısıyla bizde bu nesne üzerinden değişkenlerle ilgili bilgilere ulaşabiliyoruz.

Yukarıdaki kodun çıktısına bakarsak.

Yukarıdaki çıktıyla beraber kodu beraber incelersek her bir state nesnesi içerisinde değişkenin değerinin değiştiğini görüyoruz.

CSharpScript tipi içerisinde son olarak Create statik metodunu kullanarak da script çalıştırabiliyoruz. Ancak bu metot diğerlerinden biraz farklı. Öncelikle bu metodu çağırdığımızda parametre olarak verdiğimiz kod anında çalıştırılmıyor. Çalıştırma operasyonunu scripti yarattıktan sonra bizim tetiklememiz gerekiyor.

static void Main(string[] args)
{
    RunAsync().Wait();
}

static async Task RunAsync()
{
    var script = CSharpScript.Create("1+2");
    var state = await script.RunAsync();
}

Ayrıca burada diğer metotlardan farklı olarak script olarak verdiğimiz kodun derlenmesi RunAsync metodunu ilk çağırışımızda yapılıyor. Eğer istersek biz de Compile metodunu kullanarak derleme işlemini önceden tetikleyebiliyoruz.

static void Main(string[] args)
{
    RunAsync().Wait();
}

static async Task RunAsync()
{
    var script = CSharpScript.Create("1+2");
    script.Compile();
    var state = await script.RunAsync();
}

Script tipi üzerinden script çalıştırdığımızda bu tip içerisinde kodu derlemek için gerekli olan tüm yapılar(syntax tree vs...) saklanıyor. Bu nedenle eğer hep aynı kodu çalıştıracaksak bu yapılar bizim için ekstra yük demek. Bunun için CreateDelegate metodunu kullanarak içerisinde bu ekstra yapıları içermeyen daha basit bir tip elde edebiliriz ve bu tip üzerinde kodu daha hızlı ve efektif şekilde çalıştırabiliriz.

static void Main(string[] args)
{
    RunAsync().Wait();
}

static async Task RunAsync()
{
    var script = CSharpScript.Create("1+2");
    var runner = script.CreateDelegate();
    var result = await runner();
    Console.WriteLine(result);
}

Yukarıdaki kodu bilgisayarınızda çalıştırırsanız delegate yaratma aşamasının biraz vakit aldığını ancak sonrasında delegate'i çalıştırma işleminin çok hızlı olduğunu göreceksiniz.

Roslyn içerisinde scripting API'larının kullanımları bu şekilde. Gördüğünüz gibi API'ların hepsi oldukça kuvvetli ve çok farklı senaryolara da cevap verebilecek şekilde tasarlanmış. Bu API'lar kullanılarak Interactive Window gibi daha pek çok farklı uygulama da yapılabilir. Örneğin web sayfası üzerinden aldığınız kodları serverda çalıştırıp sonuçlarını yine web sayfası üzerinde gösterebilirsiniz. Böylece kullanıcılar için ufak bir playground yapmış olabilirsiniz.

Roslyn'in Scripting API'ları da aynı compilerlar gibi open source. Eğer bakmak isterseniz buradan ulaşabilirsiniz. Aynı şekilde Interactive Window Scripting API'ları kullanılarak nasıl yazılmış merak ediyorsanız Github üzerinden kodlarına ulaşabilirsiniz.

Bir sonraki yazıda görüşmek üzere



C# Interactive Window

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

C# Interactive Window ile ilgili ilk yazımı bundan yaklaşık 3 sene önce yazmışım. Roslyn projesi ilk duyurulduğunda projenin gösterilen en önemli kısımlarından biri Interactive Window'du. Ancak zaman geçtikçe öncelikler değişti ve interactive window'un arka planda kullandığı scripting API'larının çıkışı RTM sonrasına ertelendi ve dolayısıyla interactive window'a da ancak Visual Studio 2015 Update 1 ile kavuşabildik.

Interactive Window Nedir ?

Interactive window, C# dili ile çalışan ve zengin editör desteğine sahip bir read-eval-print-loop(REPL). Read-eval-print-loop terimini biraz daha açarsak, yazmış olduğunuz C# kodunu okuyan ve bunu arka planda çalıştırdıktan sonra da sonucunu ekrana yazan bir uygulama. Yani artık bir kodu denemek için en basitinden bir console uygulaması yaratmaya gerek kalmadan doğrudan kodu REPL uygulaması içerisinden çalıştırıp hızlı bir şekilde sonucunu görebiliriz. Bunun yanında yeni bir dil yeniliği geldiğinde de yine bu yeniliği hızlı bir şekilde REPL uygulaması içerisinde test etmemiz de mümkün.

Visual Studio 2015 Update 1'i bilgisayarımıza kurduktan sonra C# Interactive Window'u 2 şekilde açabiliriz. Bunların ilki ve en kolayı Visual Studio'nun sağ üst köşesinde bulunan Quick Launch.

Diğer seçenek ise View => Other Windows => C# Interactive üst menüsünü kullanarak açmak.

Interactive window ilk açıldığında ise aşağıdaki gibi görünüyor.

Interactive Window'un bir REPL(read-eval-print-loop) olduğundan bahsetmiştik. Dolayısıyla ilk olarak bir Hello World yazıp sonucu görmekte fayda var :)

> Console.WriteLine("Hello Interactive Window"); 
Hello Interactive Window

Gördüğümüz gibi Hello Interactive Window yazısı çalıştırdığımız kodun çıktısı olarak ekrana yazıldı. O zaman işleri biraz daha karıştıralım ve önce bir değişken tanımlayalım ve sonrasında da bu değişken üzerinden bir işlem yapalım.

> List<int> numbers = new List<int> { 4, 1, 6, 3, 9 }; 
> numbers.OrderBy(t => t).ToList().ForEach(t => Console.WriteLine(t));
1
3
4
6
9

Interactive Window içerisinde yazacağımız kodların illa tek satırdan oluşması gibi kısıt yok. Aynı kod yazar gibi Enter tuşuna basıp kod yazmaya devam edebiliriz.

> foreach(var item in numbers.OrderBy(t=>t)) 
.{
. Console.WriteLine(item);
.}
1
3
4
6
9

Interactive window içerisinde tanımladığımız değişkenler bir context içerisinde saklanıyor. Dolayısıyla önceki kod parçalarında tanımladığımız değişkenlere istediğiniz zaman window içerisinden tekrar ulaşıp kullanabiliyoruz.

Interactive Window içerisinde metotlar yazıp sonrasında bu metotları çağırmamız da mümkün.

> void PrintOrderedNumbers(List<int> unorderedList) 
.{
. unorderedList.OrderBy(t => t).ToList().ForEach(t => Console.WriteLine(t));
.}
> PrintOrderedNumbers(numbers);
1
3
4
6
9

Interactive window içerisinde kullanmak istediğimiz bir tipin bulunduğu namespace'i using ifadesi kullanarak ekleyebilmekteyiz.

> using System.Threading; 

Interactive Window içerisindeki context default olarak async tanımlı. Dolayısıyla await kullanarak istediğimiz bir async bir metodu çağırabiliriz.

> using System.Web; 
> using System.Net;
> WebClient client = new WebClient();
> var response = await client.DownloadStringTaskAsync("http://ilkayilknur.com");
> Console.WriteLine(response.Substring(0, 50));
<!DOCTYPE html> <html>
<head prefix="fb: http://

Directives

Interactive Window içerisinde bazı özel komutları kullanarak özel bazı işlemler yapmamız da mümkün. Bunlardan ilki Interactive Window içerisine dll referansı ekleme komutu'i olan #r komutu.

Referansı ekledikten sonra yukarıda görüldüğü gibi dll içerisindeki tipleri intellisense'te görebiliyoruz ve yazacağımız kodlarda bu tipleri kullanabiliyoruz.

Eğer interactive window ekranındaki kodları temizlemek istersek #cls veya #clear komutlarını kullanabiliriz. Bu komutlar sadece ekranda bulunan kodları temizliyor. Yani daha önceden tanımlamış olduğumuz değişken veya metotların hepsini koruyor. Eğer interactive window'u tamamen sıfırlamak yani tüm tanımladığınız değişkenler,metotlar vb.. tümünden kurtulmak istersek kullanacağımız komut #reset komutu olacak.

Interactive window içerisinde dışarıdan bir dosya içerisinde bulunan kodları çalıştırmamız da mümkün. Örneğin aşağıdaki kodu bir txt dosyasına kopyalayıp bilgisayarda bu dosyayı kaydedelim.

void PrintOrderedNumbers(List<int> unorderedList) 
{
unorderedList.OrderBy(t => t).ToList().ForEach(t => Console.WriteLine(t));
}
PrintOrderedNumbers(numbers);

Sonrasında interactive window içerisinde #load komutunu kullanarak bu dosya içerisindeki kodları çalıştırıp sonucunu interactive window içerisinde görebiliriz.

> #load "C:\Users\ilkay\Documents\GitHub\xomnicloud\xomni-sdk-dotnet\src\XOMNI.SDK.Public\bin\Debug\code.txt"
1
3
4
6
9

Son olarak ise interactive window içerisinde komutlarla ilgili yardım almak istersek #help komutunu kullanabiliyoruz.

Görüldüğü gibi interactive window Visual Studio içerisindeki en vazgeçilmez özelliklerden biri olmaya aday. Özellikle ufak bir kod parçasının nasıl çalıştığını görmek için veya hızlıca demolar yapmak için oldukça güzel bir ortam. Hala daha bazı eksikleri olsa da (örneğin nugetten referans ekleme) ileride bu eksik noktalar da giderilecektir diye düşünüyorum.

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