C#’da Sihirli Sealed Keyword’ü
Selamlar,
Bu makalede bir çouğumuzun bildiği ama çok da önem vermediği, Sealed keyword’ü hakkında konuşacağız.
Yazdığımız herhangi bir .NET kodu, sonunda makinanın anlayacağı dile dönüşene kadar birden fazla aşamadan geçer. Bu esnada, yazdığımız kod ne kadar net ve belirleyici olursa, derleyici bize o kadar çok yardımcı olabilir ve optimize edilmiş makine kodu oluşturabilir. Bu yazıda, derleyicinin kodumuzu optimize etmesine yardımcı olabileceğimiz yolların bir örneğini tartışacağız. Bu yollardan biri de sealed anahtar kelimesidir.
Sealed keyword’ü, C#’da sınıf tanımında kullanılır. Ve sınıfın, başka hiçbir sınıf tarafından miras alınamayacağını garanti eder.
1 |
public sealed class RabbitMQClient{} |
Bir de, sealed olarak işaretlenen methodlar vardır.
RabbitMQBase: Virtual olarak işaretli bir method, ait olduğu sınıf miras alınınca, aşağıdaki gibi override edilir.
1 2 3 4 5 6 7 |
namespace Sealed { public class RabbitMQBase { public virtual bool PushPrompt(string prompt) { return true; } } } |
RabbitMQClient: Virtual sealed olarak işaretli bir method, ait olduğu sınıf miras alınsa bile, override edilemez.
1 2 3 4 5 6 7 8 9 10 |
namespace Sealed { public class RabbitMQClient : RabbitMQBase { public override sealed bool PushPrompt(string prompt) { return true; } } } |
SpesificRabbitMQClient (HATALI): Aşağıda görülen kod hata vermektedir. Sealed olarak işaretli ne sınıf ne de method, override edilebilir. RabbitMQClient sınıfından inherit olan SpesificRabbitMQClient sınıfı, “sealed” olarak işarteli “PushPrompt()” methodunu inherit edememektedir.
1 2 3 4 5 6 7 |
namespace Sealed { public class SpesificRabbitMQClient : RabbitMQClient { public override bool PushPrompt(string prompt) { } } } |
Gelin, Sealed olarak tanımlanmış sınıfın performansını anlamak için Benchmark testi yazalım.
SealedBenchmarkTest.cs adinda, bir Console Application oluşturalım.
“BaseMathClass” adında aşağıdaki gibi Base bir Math sınıfı ve dairenin alanını hesaplayan virtual FindCircileArea() methodu aşağıdaki gibi tanımlanmıştır.
1 2 3 4 5 6 7 8 9 10 |
namespace SealedBenchmarkTest { public class BaseMathClass { public virtual double FindCircleArea(double r) { return Math.PI * Math.Pow(r,2); } } } |
MathClass.cs: Aşağıda görüldüğü gibi, Math sınıfı BaseMath sınıfından türetilip, virtual FindCircileArea() methodu override edilmiştir.
1 2 3 4 5 6 7 8 9 10 |
namespace SealedBenchmarkTest { public class MathClass : BaseMathClass { public override double FindCircleArea(double r) { return Math.PI * Math.Pow(r,2) / 2; } } } |
MathSealedClass.cs: Aşağıda görüldüğü gibi, sealed olarak işaretli MathSealed sınıfı, BaseMath sınıfından türetilip, virtual FindCircileArea() methodu gene aynı şekilde override edilmiştir.
1 2 3 4 5 6 7 8 9 10 |
namespace SealedBenchmarkTest { public sealed class MathSealedClass : BaseMathClass { public override double FindCircleArea(double r) { return Math.PI * Math.Pow(r,2) / 2; } } } |
Benchmarking.cs:
MathClass ve MathSealedClass sınıflarının FindCircleArea() methodları aşağıdaki gibi çağrılır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
using BenchmarkDotNet.Attributes; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SealedBenchmarkTest { [MemoryDiagnoser] public class Benchmarking { private readonly int NumberOfTrials = 10; private MathClass _mathClass = new(); private MathSealedClass _mathSealedClass = new(); [Benchmark] public void CallingVirtualMethodOnMyClass() { for (var i = 0; i < NumberOfTrials; i++) { _mathClass.FindCircleArea(2.12); } } [Benchmark] public void CallingVirtualMethodOnMySealedClass() { for (var i = 0; i < NumberOfTrials; i++) { _mathSealedClass.FindCircleArea(2.12); } } } } |
Program.cs: İlgili Benchmark testi aşağıdaki gibi çağrılır.
1 2 3 4 5 6 7 8 9 10 11 |
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using SealedBenchmarkTest; public class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<Benchmarking>(); } } |
Benchmark Test Sonucu: Aşağıda görüldüğü gibi Sealed sınıfı üzerinde override edilen Virtual “FindCircileArea()” Method’u, Sealed olmayan sınıfa göre çok daha performanslı çalışmıştır. Peki neden ? Çünkü compiler, bu sınıf nerelerde inherit olmuş diye bir arayış içinde olmamaktadır. Bu da doğrudan virtual methodun çalıştırılma performans’ına yansımaktadır.
Peki Sanal Olmayan Bir Method İle Kıyaslama Yapsa idik Ne Olurdu ?
BaseMathClass: Öncelikle, BaseMathClass’ina aşağıda görüldüğü gibi NonVirtual bir method ekleyelim.
1 2 3 4 5 6 7 8 9 10 11 12 |
public class BaseMathClass { public virtual double FindCircleArea(double r) { return Math.PI * Math.Pow(r,2); } public double FindCircleNonVirtualArea(double r) { return Math.PI * Math.Pow(r,2); } } |
MathClass: Math sınıfının BaseMath sınıfından türemesinden bağımsız olarak, NonVirtual methodu, kendi insiyatifinde tanımlamıştır.
1 2 3 4 5 6 7 8 9 10 11 12 |
public class MathClass : BaseMathClass { public override double FindCircleArea(double r) { return Math.PI * Math.Pow(r,2) / 2; } public double FindCircleNonVirtualArea(double r) { return Math.PI * Math.Pow(r,2)-1; } } |
MathSealedClass: Son olarak, MathSealed sınıfında NonVirtual methodu tanımlamıştır.
1 2 3 4 5 6 7 8 9 10 11 12 |
public sealed class MathSealedClass : BaseMathClass { public override double FindCircleArea(double r) { return Math.PI * Math.Pow(r,2) / 2; } public double FindCircleNonVirtualArea(double r) { return Math.PI * Math.Pow(r,2) - 1; } } |
Benchmarking.cs: Benchmark sınıfı, aşağıdaki gibi değiştirildiğinde Virtual olmayan iki methodun performans testi yapılmış olunur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
namespace SealedBenchmarkTest { [MemoryDiagnoser] public class Benchmarking { private readonly int NumberOfTrials = 10; private MathClass _mathClass = new(); private MathSealedClass _mathSealedClass = new(); [Benchmark] public void CallingVirtualMethodOnMyClass() { for (var i = 0; i < NumberOfTrials; i++) { //_mathClass.FindCircleArea(2.12); _mathClass.FindCircleNonVirtualArea(2.12); } } [Benchmark] public void CallingVirtualMethodOnMySealedClass() { for (var i = 0; i < NumberOfTrials; i++) { //_mathSealedClass.FindCircleArea(2.12); _mathSealedClass.FindCircleNonVirtualArea(2.12); } } } } |
Program.cs:İlgili Benchmark testi aşağıdaki gibi çağrılır.
1 2 3 4 5 6 7 8 9 10 11 |
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using SealedBenchmarkTest; public class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<Benchmarking>(); } } |
Aşağıda görüldüğü gibi, beklenenin çok dışında bir sonuç ile karşılaşılmaktadır. Nerede ise 2 method, birbirine çok yakın sonuçlar üretmektedir. Yukarıdaki örnek ile karşılaştırıldığında, sealed olarak işaretli bir sınıftaki sanal method ile, normal bir sınıftaki methodda benchmark testine gore 2.93sn – 8.79sn şeklinde büyük bir fark bulunmaktadır.
Buradan çıkarılacak sonuç, kaltım alınılmayacağı garanti bir sınıftaki(sealed) sanal method, derleyici tarafından çok daha performanslı çalıştırılmaktadır.
Son olarak, sınıflarınızı eger baska bir yerde kalıtıma dahil etmeyecekseniz, “Sealed” olarak işaretlemenizi tavsiye ederim. Hatta Microsoft’un başlarda sınıfları default sealed olarak işaretleme planı olduğu ama sonrasında vazgeçildiği söyleniyor.
Geldik bir makalenin daha sonuna yeni bir makalede görüşmek üzere hoşçakalın.
Kaynaklar: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/sealed
Son Yorumlar