İlkay İlknur

just a developer...

C# 7.1 Yenilikleri

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

Daha önceki blog yazılarımda ve videolarımda C#'ın minor releaselerinin çıkarılması için çalışmalar yapıldığından bahsetmiştim. Şimdiye kadar baktığımızda her yeni Visual Studio versiyonu ile beraber C#'ın da yeni versiyonu çıkıyordu. Bu nedenle hem büyük kapsamlı yenilikler hem de ufak yenilikler doğrudan tek bir versiyon içerisinde bulunuyordu. Halbuki dil içerisindeki bazı yenilikler geliştirilmesi bitse bile yeni versiyon çıkana kadar biz developerlarla buluşamıyordu.

İşte C# 7.1, bu noktada C#'ın ilk minor release'i olmakta. Visual Studio 2017 kullananlar için 15.3 versiyonu ile beraber gelirken, .NET Core'da ise 2.0 SDK'i ile beraber geliyor. Ancak bu versiyonları edinseniz bile C# 7.1 yenilikleri otomatik olarak kapalı geliyor. Bu nedenle ayrıca açmak için ufak bir ayar yapmaya ihtiyacımız var. Bunun için projemize sağ tıklayıp, Properties seçeneğini seçiyoruz ve sonra da çıkan ekrandan Build tabına geçiyoruz. En altta bir Advanced butonu olacak ve o butona tıkladığımızda ise şöyle bir ekran karşımıza gelecek.

Gördüğünüz gibi Language Version olarak latest major versiyon seçili. Yani default olarak C# 7.0 kullanılıyor. Language version seçeneklerini açtığımızda ise şu şekilde bir liste ile karşılaşıyoruz.

Buradan eğer "latest minor version" seçeneğini seçersek projede artık release olan tüm C# minor versiyonlar updateler geldikçe otomatik olarak kullanımda olacaktır. Bunları yapmak yerine belirli C# versiyonunu seçip projede hep o versiyonun kullanılmasını da sağlayabilirsiniz. Bu ayarı yaparken unutulmaması gereken bir nokta da bu ayarın build konfigürasyonu başına yapılması. Yani yaptığınız değişikliğin hem debug hem de release modda yapılmış olması gerekiyor. Eğer csproj dosyasını manuel editleyerek yapmak isterseniz bunu göz önüne almayı unutmayın. Eğer Visual Studio üzerinden yukarıdaki gibi ayar yapmak istiyorsanız da projeye sağ tıklayıp Properties'e tıkladığınızda gelen ekranda sol yukarıdaki Configuration combobox'ında All Configurations'ı seçmeyi unutmayın.

Şimdi gelelim C# 7.1 özelliklerine...

Async Main

C# 5.0 ile beraber async/await geldiğinden beri özellikle demo yaparken Main metodunun async olamaması ile ilgili hep bir istisna olduğundan bahsediyorduk. Methodu async yapamadığımız için de async bir metot çağırırken bazı farklı kullanımlarda bulunuyorduk. Artık yıllar sonra artık bu durum sona erdi ve Main metot Task veya Task<int> döndürebilir hale geldi. Main metot içerisinde async metotları gönül rahatlığıyla artık await edebiliriz 😃

Default Literals

C# içerisinde bir tipin default değerini alabilmek için default expressionları kullanabiliyorduk.

Örneğin,

var x = default(string);
var y = default(Func<stringintList<string>>); // <---- :(

Sadece string gibi tiplerin default değerini alırken yazım olarak çok zorlanmasak da özellikle kompleks generic tipler olduğunda onları yazmak bazen zor olabiliyordu. Yukarıda verdiğim ikinci örnek bu zorluğa ufak bir örnek olabilir. C# 7.1 ile beraber compiler tipini tahmin edebilidiği durumlarda artık sizin default a ekstra bir tip vermenizi zorunlu kılmıyor. Bu nedenle doğrudan default literal'i kullanabiliyorsunuz. 1-2 kullanım yaparsak örnek olarak...

static async Task Main(string[] args)
{
    Foo(default, 12);
}

static int Foo()
{
    return default;
}

static void Foo(string k, int y)
{

}

Yukarıda görüldüğü gibi metotların alacakları parametrelerde, döndüreceği değerlerde ve daha başka pek çok noktada sadece default literalini kullanabiliriz. Burada kritik nokta compilerın default değerini alacağı tipi tahmin edebilmesi. Tahmin edemediği durumlarda zaten sizden tipi ayrıca belirtmenizi isteyecektir.

Tuple isimlendirme geliştirmeleri

C# 7.0 ile beraber gelen benim en favori özelliğim tuplelar. C# 7.1 ile beraber de tuple içerisindeki fieldların isimlendirilmesi ile ilgili ufak ama güzel bir yenilik geliyor. Tuple kullanımında tuple yaratırken fieldlara dışarıdan isim verebiliyorduk.

Örneğin,

var count = 5;
var sum = 50;
var retVal = (sum: sum, count: count);

Bu şekilde kullanımlarda tuple içerisine koyduğumuz değişkenlerin isimleriyle tuple içerisindeki fieldların ismini büyük oranda aynı oluyor. Dolayısıyla aslında bunu biz belirtmesek de compiler bunu arka planda kendisi yapsa nasıl olur 😃 C# 7.1 ile artık bu mümkün

var count = 5;
var sum = 50;
var retVal = (sum, count);

Console.WriteLine($"Count:{retVal.count} , Sum:{retVal.sum}");

Yukarıda gördüğünüz gibi tuple yaratırken içerisine verdiğimiz değişkenlerin isimleriyle aynı isimde fieldlar yaratılıyor.

Gördüğünüz gibi C# 7.1 içerisinde ufak ama günlük hayatımızda kod yazarken bize yardımcı olacak özellikler gelmekte. Bir sonraki release olacak olan 7.2 release'inde çok daha farklı yenilikler geliyor olacak. Artık bu özellikler için 2 yıl yeni Visual Studio versiyonunu beklemeye gerek yok.



Visual Studio 2017 Kurumsal Lansman C# 7.0 ve Visual Studio 2017 Oturumlarım

Geçtiğimiz hafta Microsoft Türkiye ofisinde kurumsal müşteriler için Visual Studio 2017 lansmanı vardı. Bu lansmanda ben de C# 7.0 ve Visual Studio 2017 yeniliklerinden bahsettim.

Etkinliği Microsoft sadece kendi davet ettiği müşterileri için düzenlediğinden maalesef etklinlik duyurusunu paylaşamadım. Ancak Visual Studio 2017 ile ilgili bir community etkinliği olursa Twitter ve Facebook üzerinden paylaşıyor olurum.

Etkinlik organizasyonunda beni de konuşmacılar içerisine davet eden Daron Yöndem'e teşekkürler.



Visual Studio 2017 Yenilikleri Video Serisi

Bundan önceki Visual Studio versiyonlarında gelen yenilikler ile ilgili zamanında Youtube üzerinde bazı videolar paylaşmıştım. Visual Studio 2017'nin geçtiğimiz günlerde release olmasıyla beraber aynı şekilde videoları da Visual Studio 2017 için çekmeye karar verdim ve bu serinin ilk dört videosu yayında. Aşağıdan hazır olan videoları bulabilirsiniz. Yeni videoları kaçırmamak için Youtube kanalıma abone olmayı unutmayın.

Kurulum Yenilikleri

Live Unit Testing

Debugging Yenilikleri

Kod Navigasyon Yenilikleri



Task.FromResult Metodu Ne İş Yapar, Nerede Kullanılır ?

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

Bugün C# ile kod yazarken en sık kullandığımız keywordlerden ikisi şüphesiz ki async & await keywordleri. Her ne kadar bu keywordleri bilinçli olarak kullanıyoruz muyuz konusunda tam emin olamasam da bazen async & await keywordunu kullanmaktan biraz daha ötesine geçmek gerekiyor.

Task objesi bildiğimiz üzere .NET içerisinde bir asenkron operasyonla ilgili tüm bilgileri içerisinde tutan sınıf. Yani bir asenkron operasyonun o anki durumu ne ? veya işlem gerçekleşirken bir exception oldu mu ? gibi sorularımız cevaplarını alacağımız sınıf bu sınıf. Dolayısıyla C# içerisindeki async ve await keywordleri de bu task tipini kullanarak arka planda uygun bir şekilde gereken yapıları oluşturuyorlar.

Task içerisindeki FromResult metodu da en basit anlamda yeni bir Task objesi yaratarak Task'ın durumunu tamamlanmış olarak işaretliyor ve parametre olarak verdiğiniz değeri de Task'ın sonucu olarak içerisine atıyor. Bu durumda aklınıza şu soru geliyor olabilir. Normalde async & await kullandığımızda Task yaratmamız gerekmiyor. Peki bu metot neden var ? Neden ortada bir asenkron operasyon yokken Task yaratmak durumunda kalıyoruz ? Hemen kısaca kullanım alanlarına bakalım...

Interface içerisindeki asenkron metot implementasyonları

Diyelim ki 3rd party bir kütüphane kullanıyorsunuz ve o kütüphaneyi kullanabilmeniz içinde belirli bir interface'i implemente eden bir tip yaratmanız gerekiyor.

interface IThirdLib
{
    Task<int> CountAsync();
}

Bu interface içerisindeki CountAsync metodunu implemente etmek istediğinizde bulunduğunuz platformda bu işlem asenkron yapılmıyor olabilir veya static bir değer döndürmeniz gerekebilir.

class MyClass : IThirdLib
{
    public Task<int> CountAsync()
    {
        return 0;
    }
}

veya

class MyClass : IThirdLib
{
    public Task<int> CountAsync()
    {
        return Directory.EnumerateDirectories(@"C:\").Count();
    }
}

Bu durumda async await keywordu kullanmadığımız için Task tipini arka planda compiler bizim için yaratmıyor ve dolayısıyla int'i ben Task tipine çeviremem diye hata veriyor. Aklınıza hemen Task.Run yaparım onu da await ederim geliyorsa hemen o fikri unutun ve sakın yapmayın. Task.Run'ı şuursuz olarak her Task gereken yerde kullanmamak gerektiğini sakın unutmayın.

Yukarıdaki gibi bir durumla karşılırsanız Task.FromResult metodunu kullanarak tamamlanmış bir Task objesi yaratıp onu metotdan dönebilirsiniz.

class MyClass : IThirdLib
{
    public Task<int> CountAsync()
    {
        return Task.FromResult(5);
    }
}

veya

class MyClass : IThirdLib
{
    public Task<int> CountAsync()
    {
        return Task.FromResult(Directory.EnumerateDirectories(@"C:\").Count());
    }
}

Böylece bizim implementasyonumuz senkron çalışacak ama aynı zamanda bizim yazdığımız tipi kullananlar da async await kullanarak metodumuzu çağırsalar bile herhangi bir sorun yaşamayacaklar.

Bazı durumlarda senkron bazı durumlarda asenkron çalışan metotlar

Yazdığımız metotlar duruma göre senkron veya asenkron çalışabilirler. Örneğin bazı durumlarda senkron olarak validasyon yapılıp bunun sonucuna göre asenkron operasyona başlamadan exception fırlatılabilir. Örneğin MSDN'deki şu örneğe bakarsak...

static Task<long> GetFileLengthsAsync(string filePath)
{
    if (!Directory.Exists(filePath))
    {
        return Task.FromException<long>(
                    new DirectoryNotFoundException("Invalid directory name."));
    }
    else
    {
        string[] files = Directory.GetFiles(filePath);
        if (files.Length == 0)
            return Task.FromResult(0L);
        else
            return Task.Run(() => {
                long total = 0;
                Parallel.ForEach(files, (fileName) => {
                    var fs = new FileStream(fileName, FileMode.Open,
                                            FileAccess.Read, FileShare.ReadWrite,
                                            256, true);
                    long length = fs.Length;
                    Interlocked.Add(ref total, length);
                    fs.Close();
                });
                return total;
            });
    }
}

Bir klasörüdeki tüm dosyaların toplam büyüklüğünü almak istediğimizde örneğin ilk if'e bakarsanız klasörün validasyonu yapılıyor ve eğer klasör yoksa metot doğrudan geri dönüyor. Else içerisindeki 1. if'e bakarsak da klasör içerisinde hiç dosya yoksa doğrudan 0 dönüyor. Sonrasında ise Task.Run ile asenkron operasyon başlatılıyor. Dolayısıyla bu metot aslında en içteki else ifadesine kadar senkron çalışıyor. Sonrasında ise asenkron olarak devam ediyor. İşte bu gibi durumlarda da Task.FromResult metodu kullanılabilir.

Task.FromResult'a benzer diğer metotlar

Task tipi içerisinde FromResult metoduna benzeyen başka metotlar da bulabilmeniz mümkün. Örneğin, FromException veya FromCancelled gibi metotlar. Bu metotlar da isimlerindeki durumlara uyan Task tiplerini yaratıp size verirler. İçerisindeki exception olan task tipi veya iptal edilen bir Task tipi gibi...

Task.CompletedTask

.NET Framework 4.6 ile beraber Task içerisine CompletedTask propertysi eklendi. Bu Task tipi de herhangi bir değer döndürmeyen tamamlanış bir Task objesi içerisinde barındırıyor. Yani yukarıdaki gibi durumlardan birinde metodun dönüş tipi Task<int> değil de Task olsaydı Task.CompletedTask kullanabiliriz.

Taskları Cacheleme

Task.FromResult metodu her çağırdığımızda yeni bir Task objesi bize geri veriyor. Dolayısıyla her Task.FromResult(0) dediğimizde hep sonuç olarak 0 değerini barındıran bir task bize geri dönüyor.

Bu gibi durumlarda sık sık bu metodun çağırılması aslında gereksiz bir şekilde object yaratılmasına ve memoryde gereksiz bir şekilde fazladan bu objelerin yer almasına neden oluyor. Bu gibi durumlarda sıkça başvurulan yöntemlerden biri bu Taskların cachelenmesi. Bunun için örnek olarak Roslyn içerisindeki Task cacheleme mekanizmasına bakmanızı tavsiye ederim.

Roslyn içerisindeki SpecializedTasks sınıfı source code'u : https://github.com/dotnet/roslyn/blob/master/src/Workspaces/Core/Portable/Utilities/SpecializedTasks.cs



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...



BenchmarkDotNet ile Performans ve Memory Benchmarking

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

Bir ürün geliştirirken ne kadar dikkat ediyoruz bilmiyorum ama geliştirdiğimiz uygulamanın en önemli özelliklerinden biri de hiç kuşkusuz ki uygulamanın performansı. Kimi zaman "abi önce çalışsın, sonra performansa bakarız" dedikten sonra ne yazık ki o "sonra" hiçbir zaman gelmiyor taa ki müşteriden bir şikayet alana yada out of memory exception gelene kadar :D

Bu yazıda sizlere benim de arada kullandığım bir benchmarking aracı olan DotNetBenchmark'tan bahsedeceğim. Yukarıda bahsettiğim gibi müşteriden bir şikayet veya exceptionlar almadığımız sürece bu performans veya memory kullanımı olaylarına pek bakmayız. Ama bunlar geldiği zaman da kodların aralarına Stopwatch'lar koyarak süreleri veya memory'i ölçmeye çalışırız. Bu sırada yazdığımız kod da kirlenir vs.. sonra bir de bu ölçüm yaptığımız kodları toparlamak gerekir, zorlanırız. Ayrıca da ölçüm için doğru yöntemleri kullandığımızdan emin olmamız gerekiyor. BenchmarkDotNet aracı işte tüm bu pis işleri de kendisi doğru bir şekilde halletiği için de oldukça güzel bir tool. Şimdi gelelim bu toolu nasıl kullanacağımıza.

İlk olarak BenchmarkDotNet librarysini aşağıdaki komutla Nuget paketi olarak projenize ekleyebilirsiniz.

PS> Install-Package BenchmarkDotNet 

Projenize paketi ekledikten sonra artık herşey oldukça basit. Şimdi diyelim ki bir liste üzerinde LINQ sorgusu ile normal for döngüsünü karşılaştırmak istiyorsunuz. Aşağıdaki gibi basit bir class içerisinde aynı sorguyu hem LINQ ile hem de for döngüsü ile yazıyoruz. Daha sonra yapmamız gereken şey ise metotları Benchmark attribute'ü ile işaretlemek.

public class LINQvsFor
{
    private List<string> list = Enumerable.Range(1, 2000).Select(t => t + "name").ToList();

    [Benchmark]
    public List<string> For()
    {
        List<string> retVal = new List<string>();
        for (int i = 0; i < list.Count; i++)
        {
            if (list[i].Contains("1"))
            {
                retVal.Add(list[i]);
            }
        }
        return retVal;
    }
    [Benchmark]
    public List<string> LINQ()
    {
        return list.Where(t => t.Contains("1")).ToList();
    }
}

Daha sonra BenchmarkRunner sınıfını kullanarak bir console app üzerinde bu testi çalıştırıp sonucunu hızlı bir şekilde görebiliriz. Tabi sonuçları en doğru şekilde alabilmek için testi Release modda çalıştırmayı unutmamak gerekiyor :)

class Program
{
    static void Main(string[] args)
    {
        BenchmarkRunner.Run<LINQvsFor>();
    }
}

Benchmark testlerini çalıştırdıktan sonra çıktıları pek çok farklı formatta(Markdown,CSV) vs.. alabilmemiz mümkün. Bunun için basit bir config sınıfı yazıp benchmarkı çalıştırırken configi parametre olarak geçmemiz gerekiyor. Örneğin Markdown olarak export almak istersek...

class Program
{
    static void Main(string[] args)
    {
        BenchmarkRunner.Run<LINQvsFor>();
    }
}

public class Config : ManualConfig
{
    public Config()
    {
        Add(MarkdownExporter.Default);
    }
}

Benchmark testlerimizi belirli parametre kombinasyonlarında da çalıştırmamız mümkün. Bunun için ilgili fieldı Params attribute'ü ile işaretleyip alacağı değerleri belirtmemiz yeterli.

Örneğin,

public class StringConcatTest
{
    [Params(1, 2, 3, 4, 5, 10, 100, 1000)]
    public int loops;

    [Benchmark]
    public string StringConcat()
    {
        string result = string.Empty;
        for (int i = 0; i < loops; ++i)
            result = string.Concat(result, i.ToString());
        return result;
    }

    [Benchmark]
    public string StringBuilder()
    {
        StringBuilder sb = new StringBuilder(string.Empty);
        for (int i = 0; i < loops; ++i)
            sb.Append(i.ToString());
        return sb.ToString();
    }
}

Yukarıdaki testler Params attribute'ünde belirttiğimiz parametreler için teker çalıştırılacak. Sonuçlarını da belirtilen her parametre için ayrı ayrı alacağız.

Kodunuzun ne kadar memory kullandığı ile ilgili analiz yapmak isterseniz de öncelikli olarak bir ek nuget paketini daha projenize eklemeniz gerekiyor.

PS> Install-Package BenchmarkDotNet.Diagnostics.Windows 

Bu paketi de ekledikten sonra aşağıdaki gibi MemoryDiagnoser ekleyerek memory kullanımı ile ilgili sonuçları da elde edebiliriz.

class Program
{
    static void Main(string[] args)
    {
        var config = new ManualConfig();
        config.Add(DefaultConfig.Instance);
        config.Add(new MemoryDiagnoser());

        BenchmarkRunner.Run<StringConcatTest>(config);
    }
}

Uygulamayı çalıştırdığımızda...

Gördüğünüz gibi BenchmarkDotNet toolu oldukça kuvvetli bir tool. Bu yazıda bahsedemeyeceğim pek çok farklı özelliği de ayrıca içerisinde barındırmakta. Örneğin testlerini belirli ortamlar için çalıştırabiliyorsunuz. Bir testi base olarak alıp diğer testlerin sonuçlarını ona göre karşılatırabilmeniz de mümkün. Projenin temel amacı benchmarkingi kolay ve güvenilir olarak yapmak olduğu için aslında oldukça complex testleri hızlı bir şekilde 1-2 ufak değişiklikle yapabiliyorsunuz. Bu nedenle testlere başlamadan önce projenin readme sayfasını okumanızı tavsiye ederim.

Projenin Github reposunda ek olarak pekçok örnek benchmark testleri de bulunmakta. Yukarıda kullandığım string concat testi de dahil olmak üzere bu testler için de https://github.com/PerfDotNet/BenchmarkDotNet/tree/stable/BenchmarkDotNet.Samples klasörüne bakabilirsiniz.

Benchmark olayı kritik bir konu olduğu için projenin kullanıldığı yerlerin referanslarına da https://github.com/PerfDotNet/BenchmarkDotNet/wiki/People-using-BenchmarkDotNet adresinden ulaşabilirsiniz. Proje ile ilgili daha detaylı bilgiye de projenin contributerlarından olan Matt Warren'ın blogundan erişebilirsiniz.



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



C# 7.0 - Digit Separators

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

Not : Bu makaledeki örnekleri denemek için gereken ortam kurulum bilgisini buradan alabilirsiniz.

C# 7.0 ile beraber gelmesi planlanan ufak özelliklerden biri de digit separatorler. Digit separatorler ile numeric değer tanımlamaları sırasında istediğiniz bölümde basamakları ayırabiliyorsunuz. Böylece kod yazarken tanımlamış olduğunuz numeric değişkenlerin taşıdıkları değerler daha okunabilir olurken hem de siz tanımlama esnasında basamakları ayırabildiğiniz için hata yapma olasılığınız daha düşük oluyor. Digit separator olarak ise _ kullanıyoruz.

Örneğin,

int x = 1_000_000;
double y = 1_00.0_9;

Digit separatorlerin bir diğer kullanım alanı da binary literal'lar. Binary literal tanımlamaları esnasında da digit separatorleri kullanmamız mümkün.

Örneğin,

int x = 0b11_00_0_01;
double y = 0b1_00;

Bu ufak özellikle ilgili yazımızda bu kadar. Bir sonraki C# 7.0 özelliğinde görüşmek üzere...