Kodlama Yaparken Dikkat Edilmesi Gereken Bazı Performans ve Optimizasyon Teknikleri

Merhaba arkadaşlar.

Bu makale konumda sizlerle kodlama yaparken dikkat edilmesi gereken bazı performans ve optimizasyon tekniklerinden bahsedip, bunları nasıl ele almamız gerektiğini ve best practice’lerine değineceğiz. Sizlerde biliyorsunuz ki sürdürülebilir bir uygulama geliştirebilmek, hem biz yazılımcılar açısından (ileride gelecek olan yeni fonksiyonlar ve bakım işlemleri) hem de business kuralları gereği (müşteri memnuniyeti vb.) olmazsa olmaz bir ihtiyaçtır.

Bunun aksine giden bir yolda ise geliştirmiş veya geliştiriyor olduğumuz uygulama, bir süre hızla gelişen performans ihtiyaçlarının karşısında ise ayakta duramaz bir hale gelmeye mahkum olacaktır. Bu sebeple kodlama yaparken zamanla yarışmanın yanı sıra, performans ihtiyaçlarını da göz ardı etmemeliyiz.

Bir çok yazılımcı proje yöneticileri veya product owner’lar tarafından kendilerine verilen fonksiyonları geliştirmeye odaklanmaktadır. Kendilerini bu fonksiyonları geliştirmeye odaklamaları güzel fakat genellikle asıl dikkat edilmesi gereken fonksiyonel olmayan (non-functional requirements) özellikler es geçilmektedir. Üniversitelerin Yazılım Mühendisliği derslerinden hatırlarsanız, Functional ve Non-functional requirement’lar bulunmaktaydı.

Bu resim kadar daha güzel nasıl anlatılabilir bilemedim. 🙂 Non-functional ihtiyaçlar da genellikle:

  • Arayüzler
  • Kullanıcı odaklılığı
  • Güvenlik
  • Kalite
  • Performans
  • Güvence

gibi soyut niteliklerini belirleyen gereksinimlere dikkat edilmektedir. İşte tam bu noktada bizleri bir çok yazılımcıdan ayıracak olan nokta ise functional özellikleri geliştirirken, non-functional ihtiyaçlarıda göz ardı etmememiz olacaktır.

Dilerseniz şimdi kendi tecrübelerimden de edinip, farklı kaynaklardan da elde ettiğim bazı best practice’lere bir bakalım.

1) Bir method sadece bir sorumluluğu yerine getirmelidir

Bu maddede aslında dikkat etmemiz gereken asıl nokta, method özünde Single Responsibility prensibine özen göstermektir. Bu sayede bize kazandıracağı bazı avantajlar ise:

  • Method’un complexity seviyesini azaltmak
  • Hataları azaltıp, tekrar kullanılabilirliği sağlamak
  • Okunabilirliği ve genişletilebilirliği sağlamak
  • Daha tutarlı testler yazılabilir hale getirmek

gibi maddeler olacaktır. Şimdi bir örnek üzerinden giderek mevcut olanı ve olması gerekene bir bakalım.

public bool ChangePassword(int userId, string oldPassword, string newPassword)
{
    using (TestContext context = new TestContext())
    {
        var user = context.User.FirstOrDefault(u => u.Id == userId);

        if (user != null && user.Password == oldPassword && newPassword.Length >= 6)
        {
            user.Password = newPassword;
            user.LastUpdatedDate = DateTime.Now;

            context.SaveChanges();

            SmtpClient client = new SmtpClient
            {
                Host = "smtp.gmail.com",
                Port = 587,
                EnableSsl = true,
                Credentials = new System.Net.NetworkCredential("id", "password")
            };

            MailMessage mailMessage = new MailMessage("blabla@blabla.com", "blabla2@blabla.com", "Your password changed!", "bla bla bla...");
            client.Send(mailMessage);

            return true;
        }

        return false;
    }
}

Tamamen örnek amaçlı olan şifre değiştirmeye yarayan bir method düşünelim. İçerisine baktığımızda ise context üzerinden user’ı çekip, ilgili kontrollerden geçirdikten sonra şifre değiştirme işlemini gerçekleştiriyor ve hemen ardından şifre değişikliği üzerine bir mail gönderiyor. Gördüğümüz gibi buradaki method complexity’si artmış ve en önemlisi mail gönderim kısmının tekrar kullanılabilirliği kısıtlanmış durumdadır.

Optimal bir şekilde refactor etmek gerekirse:

public ActionResult UserSetting()
{
    if (ChangePassword(0, "oldPassword", "newPassword"))
    {
        SendMail();
    }
}

public bool ChangePassword(int userId, string oldPassword, string newPassword)
{
    var user = _userEngine.GetById(userId);

    // Buradaki kontroller eğer business gereği çoğalıcak ise, private bir sub method olarak da bölünebilirdi.
    if (user != null && user.Password == oldPassword && newPassword.Length >= 6)
    {
        user.Password = newPassword;
        user.LastUpdatedDate = DateTime.Now;

        context.SaveChanges();

        return true;
    }

    return false;
}

public void SendEmail()
{
    SmtpClient client = new SmtpClient
    {
        Host = "smtp.gmail.com",
        Port = 587,
        EnableSsl = true,
        Credentials = new System.Net.NetworkCredential("id", "password")
    };

    MailMessage mailMessage = new MailMessage("blabla@blabla.com", "blabla2@blabla.com", "Your password changed!", "bla bla bla...");
    client.Send(mailMessage);
}

Gördüğümüz gibi her method sadece ilgili sorumluluğunu yerine getirmektedir. SendEmail method’uda artık tekrar kullanılabilir bir hale gelmiştir.

2) Method’lar inline olarak çok fazla parametre almamalıdır

İsminden de anlaşılabildiği gibi bir method çok fazla parametre almamalıdır. Örneğin:

public bool RegisterUser(string name, string surname, string email, string password, DateTime birthDate,
    string country, string city, string address)
{

}

Bu tarz kullanımlarda farklı bir business kararı gereği değişmesi gereken veya ekstradan eklenmesi gereken bir parametre daha gerekebilir. İşte bu durumda bu method’u kullanan her yerde bu değişimleri yapmak zorunda kalabiliriz ve parametre sıralarının kayması gibi problemleri de göze almalıyız. Bunlara ek olarak da okunabilirliği azaltıyor. Çözümü ise bu parametreleri bir obje içerisinde encapsulate etmektir.

public bool RegisterUser(RegisterUserParameters registerUserParameters)
{

}

Eğer ideal parametre sayısını merak ediyorsanız “Clean Code: A Handbook of Agile Software Craftsmanship” kitabında geçen çok güzel bir dizeyi size göstermek isterim:

The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn’t be used anyway.

3) Satırlar çok fazla uzun olmamalıdır

Kod yazarken uzun uzaya giden satırlar, belli bir süre sonunda tek hamlede okunabilirliği yüksek ölçüde azaltmaktadır. Buda ilgili kodu anlamamızı veya bazı kontrol etmemiz gereken durumları gözden kaçırmamıza sebebiyet verebilir. Çözüm olarak ilgili kodun belirli parçalarını, sub method’lar halinde mantıklı bir şekilde bölmektir. Farklı kaynaklarda tavsiye edilen satır uzunlukları 200’dür.

4) Exception’lar es geçilmemelidir

Kodlamanın herhangi bir parçasında catch bloğu içerisinde es geçilen exception’lar, ilerleyen süreçlerde farklı hata ve problemlere sebebiyet verecektir. Aynı zamanda da iyi bir best practice değildir. Bu tarz durumlarda oluşan exception’ı loglamak, iyi bir yöntem olacaktır.

public void Foo()
{
    try
    {
        // TODO...
    }
    catch (System.Exception ex)
    {
    }
}

//Olması gereken
public void Foo()
{
    try
    {
        // TODO...
    }
    catch (System.Exception ex)
    {
        _logger.Log(ex);
    }
}

5) Switch-Case clause’ları çok fazla satır içermemelidir

Switch-Case clause’ları içerisindeki fazla satırlar, okunabilirliği ciddi ölçüde azaltmaktadır. Bu anlamda olabildiğince kısa statement’lar kullanılmaya çalışılmalıdır.

switch (variable)
{
    case 0:
        DoSomething();
        DoSomething2();
        DoSomething3();
        DoSomething4();
        DoSomething5();
        break;
    case 1:
        break;
    ...
}

// Olması gereken
switch (variable)
{
    case 0:
        Do();
        break;
    case 1:
        break;
    ...
}


public void Do()
{
    DoSomething();
    DoSomething2();
    DoSomething3();
    DoSomething4();
    DoSomething5();
}

Burada method çağırımının haricinde satırlarca farklı kodlar da yer alabilirdi. Okunabilirlik için olabildiğince case clause’u içerisindeki statement’ları kısaltmaya çalıştık.

6) Boolean statement’leri ters kontrol edilmemelidir

Boolean kontrollerini beklenenin aksine kontrol etmek ve ters ifadeler ile kontrol etmek hem coding standartlarına uygun değildir hem de kompleks ve maliyetli bir iştir.

if(!(variable == 2))
{
    ...
}

// Olması gereken
if(variable != 2)
{
    ...
}

7) TODO tag’ları kontrol edilmeli ve method’lara summary eklenmelidir

Eğer herhangi bir method boş ise ve daha sonra implemente edilecekse, TODO tag’ı eklenmelidir. Bu sayede TODO tag’larına göre filtrelendiğinde gözünüzden kaçan implemente edilmemiş kod parçacığı kalmayacaktır. Bunun yanı sıra method’lara eklemiş olduğunuz summary’ler sayesinde, hem siz hemde diğer ekip arkadaşlarınız kod’a henüz bakmadan summary ile kod hakkında bir fikir sahibi olabilirler.

private void DoSomething()
{
    
}

// Olması gereken
/// <summary>
/// Bu method blabla yapar
/// </summary>
private void DoSomething()
{
    //TODO: blabla bittiğinde blabla'yı buraya implemente et...
}

8) Switch-Case statement’ındaki boş DEFAULT clause’u silinmelidir

Switch-Case kullanıldığında otomatik olarak gelen DEFAULT clause’u kullanılıyorsa, o halde olması gereken uygun action kullanılmalıdır. Eğer hiç bir şey kullanılmayıp boş olarak es geçiliyorsa, kullanmanın da bir anlamı olmayacağından dolayı coding standartlarına göre silinmelidir.

switch (variable)
{
    case 0:
    //...
        break;
    case 1:
    //...
        break;
    default:
        break;
}

// Olması gereken
switch (variable)
{
    case 0:
    //...
        break;
    case 1:
    //...
        break;
    default:
    throw new NotSupportedException();
}

9) Enum tanımlarken null olarak sıfırıncı değer tanımlanmalıdır

Coding standartlarında tutarlılığı sağlayabilmek adına Enum’lar tanımlanırken, sıfırıncı değer None olarak tanımlanmalıdır.

enum MemberType
{
    Standard = 1,
    Gold = 2
}

// Olması gereken
enum MemberType
{
    None = 0,
    Standrt = 1,
    Gold = 2
}

10) Constructor aracılığı ile inject edilen field’lar, read-only olmalıdır

Constructor aracılığı ile inject ettiğimiz field’ları read-only olarak tanımlamazsak, farklı method’lar içerisinden de ilgili field’a değer atanmaya çalışılabilir, karışıklıklara sebebiyet verebilir. Bunların önüne geçebilmek için ise read-only olarak işaretlenmelidir.

class User
{
    int _userId;

    public User(int userId)
    {
        _userId = userId;
    }
}

//Olması gereken
class User
{
    readonly int _userId;

    public User(int userId)
    {
        _userId = userId;
    }
}

11) Interface tanımlanırken başına “I” prefix’i getirilmelidir

Coding standartlarına göre interface’ler tanımlanırken başlarına “I” prefix’i eklenmelidir. Örneğin:

interface IUserRepository
{
    //...
}

12) String birleştirme işlemlerinde “+” öperatörü kullanımına dikkat edilmelidir

String birleştirme işlemlerinde sadece bir kaç string’i birleştiriyor isek çok fazla problem olmayacaktır. Fakat bu işlemi daha fazla string ile gerçekleştiriyor isek memory performansı açısından StringBuilder veya String.Concat gibi işlemler uygulanmalıdır.

public void Foo()
{
    string blabla = "aaa" + "bbb" + "ccc" + "ddd" + "eee" + "ggg";
}

//Olması gereken
public void Foo()
{
    string blabla = String.Concat("aaa", "bbb", "ccc", "ddd", "eee", "ggg");
}

13) System type name’ler yerine Predefined type name’ler kullanılmalıdır

Int16, Single, UInt64 gibi system type name’ler yerine, Predefined type name’ler kullanılmalıdır. Örneğin:

String name;
Int32 userId;
Boolean isActive;

//Olması gereken
string name;
int userId;
bool isActive;

Bu sayede .Net Framework tarafında daha tutarlı bir okuma gerçekleştirecektir.

14) External kaynaklara erişecek bir class varsa IDisposable pattern’ini implemente edilmelidir

IO işlemleri, web servisler, sql gibi external kaynaklara ihtiyaç duyduğunuz durumlar olduğunda, memory performansı için IDisposable pattern’ini implemente edin.

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
    if (disposing)
    {
        //Managed objeler burada yönetilecek
    }
    //Unmanaged objeler ise burada yönetilecek
    //Büyük field'lar null'a çekilecek vb.
}

~Base()
{
    Dispose(false);
}

15) Kodlama yaparken kodlar içerisinde magic string/numbers kullanılmamalıdır

İlgili kod parçacıkları içerisinde kullanılan magic string/number’lar tekrar kullanılabilirliği engellemekle kalmayıp, ilgili değer değiştiğinde ise her nerede kullanıldıysa tek tek bulunup güncellenmesi gerekmektedir. Coding standartları doğrultusunda bu tarz değişkenler global ve const olarak tanımlanmalıdır.

public void Foo(int interval)
{
    int a = 3 * interval;
    int b = a / 5;
}

//Olması gereken
public class Blabla
{
    private const int SomeVariable = 3;
    private const int DifferentVariable = 5;

    public void Foo(int interval)
    {
        int a = SomeVariable * interval;
        int b = a / DifferentVariable;
    }
}

16) Unmanaged kaynaklar için using statement’ı kullanılmalıdır

Bildiğimiz gibi .Net Framework içerisindeki Garbage collector hiçbir referans tarafından gösterilmeyen managed nesneleri bellekten kaldırmaktadır. Ancak Garbage collector unmanaged kodlar üzerinde tam kontrole sahip değildir. Kullanılmıyor olsalar bile bellekten serbest bırakamaz. İşte bu noktada biz yazılımcılar tarafından Dispose edilmesi gerekmektedir. Bu tarz unmanaged kaynaklara erişen objeler genelde IDisposable arayüzünü implemente etmektedir. Bu nesneleri dispose edebilmenin en iyi yolu ise, using statement’ı içerisinde kullanmaktır.

using(SqlConnection sqlConnection = new SqlConnection("ConnectionString"))
{
    using(SqlCommand command = new SqlCommand("", sqlConnection))
    {
        //...
    }
}

17) Catch bloğunda exception tekrardan fırlatılırken sadece throw kullanılmalıdır

Catch bloğu içerisinde ilgili exception’ı logladıktan sonra tekrardan geriye fırlatma ihtiyacı duyulursa, bunu sadece throw keyword’ü kullanılarak gerçekleştirilmelidir. Aksi durumda oluşan orjinal exception’ın stack trace’i kaybolacaktır.

catch(Exception ex)
{
  _logger.Log(ex);
  throw ex;
}

//Olması gereken
catch(Exception ex)
{
  _logger.Log(ex);
  throw;
}

 

Sizlerle önemli olarak ele aldığım ve alınan, code review’ler sırasında da dikkat ettiğim, farklı kaynaklardan da derlediğim best practice’leri paylaşmaya çalıştım. Umarım herkes için faydalı bir makale olur.

Takipte kalın.

Kaynaklar:

http://www.dofactory.com/reference/csharp-coding-standards
http://www.codeproject.com/Articles/8971/C-Coding-Standards-and-Best-Programming-Practiceshttp://se.inf.ethz.ch/old/teaching/ss2007/251-0290-00/project/CSharpCodingStandards.pdf
http://www.c-sharpcorner.com/article/coding-standard-best-practices-in-c-sharp/

Gökhan Gökalp

View Comments

  • Bahsi geçen önerilerin hiç biri performansı etkileyecek bir durum oluşturmaz. En fazla performansa yakınlığı olan konu, switch ve boş default durumudur ki bu bile göz ardı edilebilir. Konu yönetilebilirlik olsaydı tamamdı ama performans adına sonuna kadar anlamsız bir başlık olmuş. Bence kodlamaya bakış açınızı değiştirip alıntı yazıları sorgulamadan kabullenmekten kaçının.

    • Merhabalar, yorumunuz ve önerileriniz için teşekkür ederim. Buradaki maddelere kapalı bir boru içerisinden bakmamalıyız. Örneğin 1. madde olan "Bir method sadece bir sorumluluğu yerine getirmelidir" maddesinde, mail gönderim işlemi farklı bir method'a ayrıldığından dolayı buradaki işlem asenkron olarak gerçekleştirilebilir. Buda performans etkenini etkilemektedir. Switch-Case durumları da göz ardı edilmemelidir eğer clean bir code geliştiriyorsak. Bir diğeri 12. maddeye baktığımızda ise sayın Çağatay Doğan bey, string birleştirme ile StringBuilder arasındaki ciddi performans farklarının da olduğunu sizin iyi bilmeniz gerekir. 14. maddeye hemen geldiğimizde ise "External kaynaklara erişecek bir class varsa IDisposable pattern’ini implemente edilmelidir" uygun bir bellek yönetimi gerçekleştirmediğimiz de ilgili application farm'ı git gide yavaşlayıp, allocate olacaktır. Bunu takiben 16. maddeye baktığımızda ise, "Unmanaged kaynaklar için using statement’ı kullanılmalıdır" yine bir bellek yönetimi pratiği görüyoruz. Diğer kalanlar ise optimize bir şekilde temiz kod yazabilmek için olması gereken önerilerdir. Ben burada performansa ve optimizasyona, yönetilebilirliğe vb. alt maddelere yönelik bir kod yazarken nelere dikkat edebiliriz gibi coding standartlarını aktarmaya çalıştım. Bunun haricinde konumuz ORM kullanımı/seçimi, Distributed cache/session kullanımları, Javascript Framework seçimleri gibi architectural konular değil. Yinede yorumunuz için teşekkür ederim.

  • Merhaba Gökhan Bey,

    Yazınıza eleştirim, uzun yıllardır performans konusuna oldukça takıntılı bir insan olarak yapıcı bir eleştiri niteliğindir, kişisel algılamamanızı rica ederim. Mikro performans oldukça hassas bir konu. Yıllardır compiler üzerindeki geliştirmelerin etkisi ile her .Net versiyonunda bir öncekinde d, bir sonrakindeğişmekte, geçersiz kılınmakta veya ters etki yaratabilmekte. Adım adım takip etmediğimiz durumda, konu "doğru bildiğimiz yanlışlar" kategorisine girivermekte. Örnek verecek olursam; string.concat olayı yanlış bilmiyorsam .Net 2.0'dan bu tarafa bahsettiğiniz gibi çalışmıyor. Compiler'ı geliştiren arkadaşlar developer alışkanlıklarını da göz önünde bulundurarak bu durumları konu özelinde ele alıp performans arttıracak önlemleri alıyorlar. Aşağıdaki kod örneği bahsettiğiniz konuya çok derinlemesine olmasa da detaylı bakan bir örnek.
    using System;

    namespace ConsoleApplication51
    {
    class Program
    {
    public static string Foo1()
    {
    return "a" + "1" + "b" + "2" + "c" + "3";
    }

    public static string Foo2()
    {
    return "a" + 1.ToString() + "b" + 2.ToString() + "c" + 3.ToString();
    }

    public static string Foo3()
    {
    var s = "a";
    s += "1";
    s += "b";
    s += "2";
    s += "c";
    s += "3";

    return s;
    }

    public static string Foo4()
    {
    var s = "a";
    s += 1.ToString();
    s += "b";
    s += 2.ToString();
    s += "c";
    s += 3.ToString();

    return s;
    }

    public static string Foo5()
    {
    return String.Concat("a", "1", "b", "2", "c", "3");
    }

    public static string Foo6()
    {
    return String.Concat("a", 1.ToString(), "b", 2.ToString(), "c", 3.ToString());
    }

    public static string Foo7()
    {
    var s = "a";
    s = String.Concat("1", s);
    s = String.Concat("b", s);
    s = String.Concat("2", s);
    s = String.Concat("c", s);
    s = String.Concat("3", s);

    return s;
    }

    public static string Foo8()
    {
    var s = "a";
    s = String.Concat(1.ToString(), s);
    s = String.Concat("b", s);
    s = String.Concat(2.ToString(), s);
    s = String.Concat("c", s);
    s = String.Concat(3.ToString(), s);

    return s;
    }

    static void Main(string[] args)
    {
    Foo1();
    Foo2();
    Foo3();
    Foo4();
    Foo5();
    Foo6();
    Foo7();
    Foo8();

    Console.ReadKey();
    }
    }
    }

    Burada 4 örnek göstermek istedim. Bu örneklerin IL kodlarına bakarsanız aşağıdaki kod örgülerini görürsünüz. Bu açıdan bakarsanız en performanslı kod bloğunun Foo1 metodu tarafından sağlandığını görürsünüz çünkü IL optimizasyonu ile "a" + "1" + "b" + "2" + "c" + "3" kodunu optimize edip "a1b2c3" haline dönüştürmüştür. Diğer Foo2 metoduna da dikkat ederseniz zaten compiler sizin yerinize buradaki işlemi string.concat işlemine dönüştürür. Compiler'ın optimize edemediği Foo3 ve Foo4 metodlarındaki durumdur. Bu durum tam da StringBuilder'a ihtiyacımızın olduğu yerdir. Foo1 metodunun kötü tarafı ise okunaklı olmamasıdır. Email adresim sizde mevcut, arzu ederseniz makalede geçen diğer öneriler hakkındaki görüşlerimi de email üzerinden iletişime geçerseniz ayrıca paylaşabilirim. Yorumumda yazım hatalarım varsa da lütfen kusuruma bakmayın.

    .namespace ConsoleApplication51
    {
    .class private auto ansi beforefieldinit ConsoleApplication51.Program
    extends [mscorlib]System.Object
    {
    // Methods
    .method public hidebysig static
    string Foo1 () cil managed
    {
    // Method begins at RVA 0x2050
    // Code size 11 (0xb)
    .maxstack 1
    .locals init (
    [0] string CS$1$0000
    )

    IL_0000: nop
    IL_0001: ldstr "a1b2c3"
    IL_0006: stloc.0
    IL_0007: br.s IL_0009

    IL_0009: ldloc.0
    IL_000a: ret
    } // end of method Program::Foo1

    .method public hidebysig static
    string Foo2 () cil managed
    {
    // Method begins at RVA 0x2068
    // Code size 79 (0x4f)
    .maxstack 3
    .locals init (
    [0] string CS$1$0000,
    [1] string[] CS$0$0001,
    [2] int32 CS$0$0002
    )

    IL_0000: nop
    IL_0001: ldc.i4.6
    IL_0002: newarr [mscorlib]System.String
    IL_0007: stloc.1
    IL_0008: ldloc.1
    IL_0009: ldc.i4.0
    IL_000a: ldstr "a"
    IL_000f: stelem.ref
    IL_0010: ldloc.1
    IL_0011: ldc.i4.1
    IL_0012: ldc.i4.1
    IL_0013: stloc.2
    IL_0014: ldloca.s CS$0$0002
    IL_0016: call instance string [mscorlib]System.Int32::ToString()
    IL_001b: stelem.ref
    IL_001c: ldloc.1
    IL_001d: ldc.i4.2
    IL_001e: ldstr "b"
    IL_0023: stelem.ref
    IL_0024: ldloc.1
    IL_0025: ldc.i4.3
    IL_0026: ldc.i4.2
    IL_0027: stloc.2
    IL_0028: ldloca.s CS$0$0002
    IL_002a: call instance string [mscorlib]System.Int32::ToString()
    IL_002f: stelem.ref
    IL_0030: ldloc.1
    IL_0031: ldc.i4.4
    IL_0032: ldstr "c"
    IL_0037: stelem.ref
    IL_0038: ldloc.1
    IL_0039: ldc.i4.5
    IL_003a: ldc.i4.3
    IL_003b: stloc.2
    IL_003c: ldloca.s CS$0$0002
    IL_003e: call instance string [mscorlib]System.Int32::ToString()
    IL_0043: stelem.ref
    IL_0044: ldloc.1
    IL_0045: call string [mscorlib]System.String::Concat(string[])
    IL_004a: stloc.0
    IL_004b: br.s IL_004d

    IL_004d: ldloc.0
    IL_004e: ret
    } // end of method Program::Foo2

    .method public hidebysig static
    string Foo3 () cil managed
    {
    // Method begins at RVA 0x20c4
    // Code size 73 (0x49)
    .maxstack 2
    .locals init (
    [0] string s,
    [1] string CS$1$0000
    )

    IL_0000: nop
    IL_0001: ldstr "a"
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: ldstr "1"
    IL_000d: call string [mscorlib]System.String::Concat(string, string)
    IL_0012: stloc.0
    IL_0013: ldloc.0
    IL_0014: ldstr "b"
    IL_0019: call string [mscorlib]System.String::Concat(string, string)
    IL_001e: stloc.0
    IL_001f: ldloc.0
    IL_0020: ldstr "2"
    IL_0025: call string [mscorlib]System.String::Concat(string, string)
    IL_002a: stloc.0
    IL_002b: ldloc.0
    IL_002c: ldstr "c"
    IL_0031: call string [mscorlib]System.String::Concat(string, string)
    IL_0036: stloc.0
    IL_0037: ldloc.0
    IL_0038: ldstr "3"
    IL_003d: call string [mscorlib]System.String::Concat(string, string)
    IL_0042: stloc.0
    IL_0043: ldloc.0
    IL_0044: stloc.1
    IL_0045: br.s IL_0047

    IL_0047: ldloc.1
    IL_0048: ret
    } // end of method Program::Foo3

    .method public hidebysig static
    string Foo4 () cil managed
    {
    // Method begins at RVA 0x211c
    // Code size 85 (0x55)
    .maxstack 2
    .locals init (
    [0] string s,
    [1] string CS$1$0000,
    [2] int32 CS$0$0001
    )

    IL_0000: nop
    IL_0001: ldstr "a"
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: ldc.i4.1
    IL_0009: stloc.2
    IL_000a: ldloca.s CS$0$0001
    IL_000c: call instance string [mscorlib]System.Int32::ToString()
    IL_0011: call string [mscorlib]System.String::Concat(string, string)
    IL_0016: stloc.0
    IL_0017: ldloc.0
    IL_0018: ldstr "b"
    IL_001d: call string [mscorlib]System.String::Concat(string, string)
    IL_0022: stloc.0
    IL_0023: ldloc.0
    IL_0024: ldc.i4.2
    IL_0025: stloc.2
    IL_0026: ldloca.s CS$0$0001
    IL_0028: call instance string [mscorlib]System.Int32::ToString()
    IL_002d: call string [mscorlib]System.String::Concat(string, string)
    IL_0032: stloc.0
    IL_0033: ldloc.0
    IL_0034: ldstr "c"
    IL_0039: call string [mscorlib]System.String::Concat(string, string)
    IL_003e: stloc.0
    IL_003f: ldloc.0
    IL_0040: ldc.i4.3
    IL_0041: stloc.2
    IL_0042: ldloca.s CS$0$0001
    IL_0044: call instance string [mscorlib]System.Int32::ToString()
    IL_0049: call string [mscorlib]System.String::Concat(string, string)
    IL_004e: stloc.0
    IL_004f: ldloc.0
    IL_0050: stloc.1
    IL_0051: br.s IL_0053

    IL_0053: ldloc.1
    IL_0054: ret
    } // end of method Program::Foo4

    .method public hidebysig static
    string Foo5 () cil managed
    {
    // Method begins at RVA 0x2180
    // Code size 67 (0x43)
    .maxstack 3
    .locals init (
    [0] string CS$1$0000,
    [1] string[] CS$0$0001
    )

    IL_0000: nop
    IL_0001: ldc.i4.6
    IL_0002: newarr [mscorlib]System.String
    IL_0007: stloc.1
    IL_0008: ldloc.1
    IL_0009: ldc.i4.0
    IL_000a: ldstr "a"
    IL_000f: stelem.ref
    IL_0010: ldloc.1
    IL_0011: ldc.i4.1
    IL_0012: ldstr "1"
    IL_0017: stelem.ref
    IL_0018: ldloc.1
    IL_0019: ldc.i4.2
    IL_001a: ldstr "b"
    IL_001f: stelem.ref
    IL_0020: ldloc.1
    IL_0021: ldc.i4.3
    IL_0022: ldstr "2"
    IL_0027: stelem.ref
    IL_0028: ldloc.1
    IL_0029: ldc.i4.4
    IL_002a: ldstr "c"
    IL_002f: stelem.ref
    IL_0030: ldloc.1
    IL_0031: ldc.i4.5
    IL_0032: ldstr "3"
    IL_0037: stelem.ref
    IL_0038: ldloc.1
    IL_0039: call string [mscorlib]System.String::Concat(string[])
    IL_003e: stloc.0
    IL_003f: br.s IL_0041

    IL_0041: ldloc.0
    IL_0042: ret
    } // end of method Program::Foo5

    .method public hidebysig static
    string Foo6 () cil managed
    {
    // Method begins at RVA 0x21d0
    // Code size 79 (0x4f)
    .maxstack 3
    .locals init (
    [0] string CS$1$0000,
    [1] string[] CS$0$0001,
    [2] int32 CS$0$0002
    )

    IL_0000: nop
    IL_0001: ldc.i4.6
    IL_0002: newarr [mscorlib]System.String
    IL_0007: stloc.1
    IL_0008: ldloc.1
    IL_0009: ldc.i4.0
    IL_000a: ldstr "a"
    IL_000f: stelem.ref
    IL_0010: ldloc.1
    IL_0011: ldc.i4.1
    IL_0012: ldc.i4.1
    IL_0013: stloc.2
    IL_0014: ldloca.s CS$0$0002
    IL_0016: call instance string [mscorlib]System.Int32::ToString()
    IL_001b: stelem.ref
    IL_001c: ldloc.1
    IL_001d: ldc.i4.2
    IL_001e: ldstr "b"
    IL_0023: stelem.ref
    IL_0024: ldloc.1
    IL_0025: ldc.i4.3
    IL_0026: ldc.i4.2
    IL_0027: stloc.2
    IL_0028: ldloca.s CS$0$0002
    IL_002a: call instance string [mscorlib]System.Int32::ToString()
    IL_002f: stelem.ref
    IL_0030: ldloc.1
    IL_0031: ldc.i4.4
    IL_0032: ldstr "c"
    IL_0037: stelem.ref
    IL_0038: ldloc.1
    IL_0039: ldc.i4.5
    IL_003a: ldc.i4.3
    IL_003b: stloc.2
    IL_003c: ldloca.s CS$0$0002
    IL_003e: call instance string [mscorlib]System.Int32::ToString()
    IL_0043: stelem.ref
    IL_0044: ldloc.1
    IL_0045: call string [mscorlib]System.String::Concat(string[])
    IL_004a: stloc.0
    IL_004b: br.s IL_004d

    IL_004d: ldloc.0
    IL_004e: ret
    } // end of method Program::Foo6

    .method public hidebysig static
    string Foo7 () cil managed
    {
    // Method begins at RVA 0x222c
    // Code size 73 (0x49)
    .maxstack 2
    .locals init (
    [0] string s,
    [1] string CS$1$0000
    )

    IL_0000: nop
    IL_0001: ldstr "a"
    IL_0006: stloc.0
    IL_0007: ldstr "1"
    IL_000c: ldloc.0
    IL_000d: call string [mscorlib]System.String::Concat(string, string)
    IL_0012: stloc.0
    IL_0013: ldstr "b"
    IL_0018: ldloc.0
    IL_0019: call string [mscorlib]System.String::Concat(string, string)
    IL_001e: stloc.0
    IL_001f: ldstr "2"
    IL_0024: ldloc.0
    IL_0025: call string [mscorlib]System.String::Concat(string, string)
    IL_002a: stloc.0
    IL_002b: ldstr "c"
    IL_0030: ldloc.0
    IL_0031: call string [mscorlib]System.String::Concat(string, string)
    IL_0036: stloc.0
    IL_0037: ldstr "3"
    IL_003c: ldloc.0
    IL_003d: call string [mscorlib]System.String::Concat(string, string)
    IL_0042: stloc.0
    IL_0043: ldloc.0
    IL_0044: stloc.1
    IL_0045: br.s IL_0047

    IL_0047: ldloc.1
    IL_0048: ret
    } // end of method Program::Foo7

    .method public hidebysig static
    string Foo8 () cil managed
    {
    // Method begins at RVA 0x2284
    // Code size 85 (0x55)
    .maxstack 2
    .locals init (
    [0] string s,
    [1] string CS$1$0000,
    [2] int32 CS$0$0001
    )

    IL_0000: nop
    IL_0001: ldstr "a"
    IL_0006: stloc.0
    IL_0007: ldc.i4.1
    IL_0008: stloc.2
    IL_0009: ldloca.s CS$0$0001
    IL_000b: call instance string [mscorlib]System.Int32::ToString()
    IL_0010: ldloc.0
    IL_0011: call string [mscorlib]System.String::Concat(string, string)
    IL_0016: stloc.0
    IL_0017: ldstr "b"
    IL_001c: ldloc.0
    IL_001d: call string [mscorlib]System.String::Concat(string, string)
    IL_0022: stloc.0
    IL_0023: ldc.i4.2
    IL_0024: stloc.2
    IL_0025: ldloca.s CS$0$0001
    IL_0027: call instance string [mscorlib]System.Int32::ToString()
    IL_002c: ldloc.0
    IL_002d: call string [mscorlib]System.String::Concat(string, string)
    IL_0032: stloc.0
    IL_0033: ldstr "c"
    IL_0038: ldloc.0
    IL_0039: call string [mscorlib]System.String::Concat(string, string)
    IL_003e: stloc.0
    IL_003f: ldc.i4.3
    IL_0040: stloc.2
    IL_0041: ldloca.s CS$0$0001
    IL_0043: call instance string [mscorlib]System.Int32::ToString()
    IL_0048: ldloc.0
    IL_0049: call string [mscorlib]System.String::Concat(string, string)
    IL_004e: stloc.0
    IL_004f: ldloc.0
    IL_0050: stloc.1
    IL_0051: br.s IL_0053

    IL_0053: ldloc.1
    IL_0054: ret
    } // end of method Program::Foo8

    .method private hidebysig static
    void Main (
    string[] args
    ) cil managed
    {
    // Method begins at RVA 0x22e5
    // Code size 56 (0x38)
    .maxstack 8
    .entrypoint

    IL_0000: nop
    IL_0001: call string ConsoleApplication51.Program::Foo1()
    IL_0006: pop
    IL_0007: call string ConsoleApplication51.Program::Foo2()
    IL_000c: pop
    IL_000d: call string ConsoleApplication51.Program::Foo3()
    IL_0012: pop
    IL_0013: call string ConsoleApplication51.Program::Foo4()
    IL_0018: pop
    IL_0019: call string ConsoleApplication51.Program::Foo5()
    IL_001e: pop
    IL_001f: call string ConsoleApplication51.Program::Foo6()
    IL_0024: pop
    IL_0025: call string ConsoleApplication51.Program::Foo7()
    IL_002a: pop
    IL_002b: call string ConsoleApplication51.Program::Foo8()
    IL_0030: pop
    IL_0031: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
    IL_0036: pop
    IL_0037: ret
    } // end of method Program::Main

    .method public hidebysig specialname rtspecialname
    instance void .ctor () cil managed
    {
    // Method begins at RVA 0x231e
    // Code size 7 (0x7)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: call instance void [mscorlib]System.Object::.ctor()
    IL_0006: ret
    } // end of method Program::.ctor

    } // end of class ConsoleApplication51.Program
    }

    • Merhaba Çağatay Bey tekrardan. Kişisel olarak algılamadım sadece alıntı yazıları kabullenmekten kaçının kısmı biraz fazla yapıcı oldu. Onun dışında gelecek olursak, sizin yaptığınız her öneri benim gerek kendimi geliştirmede gerekse farklı boyutlarını görme konusunda önemlidir. Sonuç olarak aramızda çok fazla yaş ve deneyimler bulunmakta. O yüzden size saygım sonsuz. Göstermiş olduğunuz string concatenation örnekleri gayet açıklayıcı. Benimde bir üst yorumda belirtmek istediğimde/çalıştığımda bu idi en basitinden string ile StringBuilder kullanım yeri/farkı. Bunun üzerine zaten bir çok performans testleri bulunmakta. Örneklerinize ek olarak bende bir loop içerisinde özellikle sürekli string initialazing yerine StringBuilder kullanımının da efektif olacağını eklemek isterim. Makale konusunda ise, bende eğer bir kaç string concatenation işlemi varsa problem olmayacağını belirtmeye çalıştım. Belkide dediğiniz gibi burada biraz daha konuyu açarak, bu concatenation işlemlerinin kullanım boyutlarına göre sizin örneklerinizde de olduğu gibi farklılık göstereceğini de eklemem gerekirdi. Tekrardan güzel yorumlarınız için teşekkür ederim ve diğer örnekler için fikirlerinizi almak adına e-posta ile iletişime geçeceğim.

  • Gökhan Bey,

    Yazınıza eleştirimi yanlış anlamış olabilirsiniz diye belirtmek istedim, bir çok yazınızı zaman buldukça, rastgeldikçe, severek ve beğenerek okuyorum.

    Performans konusuna gelecek olursak da nacizane önerim, Linq ve performans konusuna detaylı bir şekilde değinen bir yazı ele almanız olurdu.

    Uzun zamandır bu konuyu hakkıyla becerenler gördüm ama gördüğüm becemeyenlerin sayısı o kadar fazla ki, bu konudaki düşüncem; "Functional Programming" bilhassa özel ilgi alanınız değilse, Linq ve Lamda expression'a da arka planda neler döndüğünü anlayabilecek kadar hakim değilseniz veya anlamaya çalışmıyorsanız, mümkün olduğunca uzak durulması gerektiği. Nedeni ise çok basit, Linq ve Lamda expression hayatı kolaylaştırıyor gibi görünmesine rağmen bilinçsiz ve tecrübesiz kullanımlarla, doğru işi yaptığı sanılarak yazılan kodların bilhassa çok sık kullanılan yerlerde performansın yerlerde sürünmesine sebep olması.

    Tabiki hangi konuya değineceğiniz size kalmış bir durum. Buna müdahil olmak da haddime değil ama bu konudaki detaylı bir yazıyla, bir çok açıdan değinilmesi gereken ama pek fazla da değinilmeyen bir konuda, yazınız çok faydalı olabilir diye düşünüyorum.

    Saygılar,

    Çağatay DOĞAN

    • Çağatay Bey tekrardan teşekkür ederim belirtmeniz için. Özelikle Linq ve EntityFramework performansı konusunda çok haklısınız. Çoğu zaman yazılımcı arkadaşların yazdıkları kodu farklı profiler tool'ları ile profiling etmekteyiz. En basitinden sizinde bildiğiniz üzere, tracking change'ler yüzünden gereksiz alanlarında update'e gitmesi, kullanması gereken property'ler yerine tüm entity'i çekmesi gibi vb. Bunun dışında zaten kaç kişi arka planda Linq provider'ının ilgili expression'ları nasıl evulate ettiğini, oradaki Visitor Pattern yapısını biliyor ve araştırma gereği duyuyor. Güzel öneriniz için çok teşekkür ederim daha önce de aklıma gelmişti fakat dediğiniz gibi micro performans konuları derinlemesine bir konu ve gerçekten zaman ve araştırma gerektiriyor yazıya dökebilmek için. Herhangi bir saygısızlığım oldu ise daha önceki yazımımda affola.

      Saygılarımla.

  • Kesinlikle 11'inci kurala katilmiyorum, mimariyi belirten seyler koda katmak kesinlikle yanlistir. Editorler bunun icin vardir.

    • Koda katmak kesinlikle yanlıştır, editörler bunun için vardır cümlenizi anlayamadım fakat, mimariyi belirten bir case değil bu sadece naming convention. Katılıp katılmamak size kalmış. :)

  • MErhaba,

    Guzel bir yazi olmus elinize saglik. Sanki butun bu yorumlarin sebebi baslik seciminizden kaynaklaniyor gibi :)

    Performans deyince daha farkli birseyler beklemistim acikcasi.

    • Merhaba teşekkür ederim yorumunuz ve öneriniz için. Evet, olabilir başlık seçimi. :)

Recent Posts

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Policy Enforcement-Automated Governance with OPA Gatekeeper and Ratify) – Part 2

{:tr} Makalenin ilk bölümünde, Software Supply Chain güvenliğinin öneminden ve containerized uygulamaların güvenlik risklerini azaltabilmek…

6 months ago

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Security Scanning, SBOMs, Signing&Verifying Artifacts) – Part 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

8 months ago

Delegating Identity & Access Management to Azure AD B2C and Integrating with .NET

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

1 year ago

How to Order Events in Microservices by Using Azure Service Bus (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

2 years ago

Providing Atomicity for Eventual Consistency with Outbox Pattern in .NET Microservices

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

2 years ago

Building Microservices by Using Dapr and .NET with Minimum Effort – 02 (Azure Container Apps)

{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…

2 years ago