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:
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.
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:
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.
İ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.
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.
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); } }
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.
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) { ... }
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... }
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(); }
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 }
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; } }
Coding standartlarına göre interface’ler tanımlanırken başlarına “I” prefix’i eklenmelidir. Örneğin:
interface IUserRepository { //... }
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"); }
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.
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); }
İ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; } }
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)) { //... } }
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/
{:tr} Makalenin ilk bölümünde, Software Supply Chain güvenliğinin öneminden ve containerized uygulamaların güvenlik risklerini azaltabilmek…
{: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.…
{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…
{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…
{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…
{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…
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.
Hocam sabah sabah ufkumuzu açtın :) Eline sağlık çok faydalı bir makale.
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ış. :)
Performanstan ziyade kod okunurluğu işlenmiş. Elinize sağlık
Meyve veren ağaç taşlanır hocam :))
Yanlış bir şey yok, herkesin kendisine göre bir doğrusu var diyelim. :)
Emeğinize sağlık hocam, kitapınızıda bitirdim harika bilgiler veriyorsunuz
Teşekkür ederim güzel yorumunuz için. :)
Faydalı olmuş, teşekkürler.
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. :)