c#에서는 디폴트로 기본 클래스들은 상속할 수 있습니다. 상속시킬 생각이 없는 클래스라면 sealed한정자를 사용하는게 성능을 미세하게 향상시킬 수 있습니다.
클래스나 메소드, 프로퍼티에 사용할 수 있는 한정자입니다.
클래스에 사용할 경우 다른 클래스는 해당 클래스를 상속 받을 수 없습니다.
1
2
3
4
class A {}
sealed class B : A {}
// 다른 클래스는 B 클래스를 상속받지 못함
// class C : B { }
메소드의 경우에는 override라는 한정자가 있는 메소드에만 사용할 수 있고 해당 메소드를 포함하고 있는 클래스를 다른 클래스가 상속받았을 때 해당 메소드는 재정의가 불가능합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class X
{
protected virtual void F() { Console.WriteLine("X.F"); }
}
class Y : X
{
sealed protected override void F() { Console.WriteLine("Y.F"); }
}
class Z : Y
{
// Attempting to override F causes compiler error CS0239.
// protected override void F() { Console.WriteLine("Z.F"); }
}
공식 문서에서는 파생 클래스가 당신의 클래스를 커스텀할 때 얻을 수 있는 잠재적 이득이 있는지 혹은 수정했을 때 기대와 다르게 올바르지 않게 동작할 가능성이 있는지 고려를 하여 sealed 한정자를 사용하라 합니다.
처음에는 굳이 제한하기만 하는 한정자를 사용할 필요가 있나라는 생각과 함께 이 한정자에 대해 깊게 고민해보지 않았습니다.
하지만 조금 더 리서치를 진행해보니 퍼포먼스와 관련된 블로그들이 보이고 꼭 기억해야할 내용이라는 생각에 추가로 정리해봅니다.
자세한 벤치마킹은 다른 블로그들을 참고바랍니다.
각각의 클래스들은 모든 가상 메소드들의 주소를 포함하고 있는 가상 메소드 테이블을 가지고 있습니다. 가상메소드를 호출하면 런타임 시점에 객체의 실제 타입(클래스)에 기반하여 실제 메소드를 찾습니다.
JIT 컴파일러가 객체의 실제 타입을 알 수 있면 가상 테이블을 찾는걸 스킵하고 바로 호출해줄 수 있습니다. sealed 한정자를 사용하면 적어도 더이상 상속받는 클래스가 없다는 것을 알려줄 수 있습니다.
참고로 객체가 메소드 내에서 생성되고 수정이 없다면 JIT도 sealed 한정자가 없더라도 타입을 알기 때문에 직접 호출을 합니다.
is / as 연산자as 연산자와 is 연산자는 런타임에 해당 타입과 호환되는지 체크를 하는데 sealed 한정자가 없으면 그 계층구조에 있는 모든 클래스를 체크해야하는데 sealed 한정자가 있으면 딱 그 타입만 체크하면되어 빠르게 동작합니다.
배열은 공변성을 가지고 있습니다. 따라서 is / as 연산자와 유사하게 배열에 원소를 할당할 때 마다 체크를 해야하는데 sealed 한정자가 있으면 체크를 생략하여 빠르게 동작합니다.
가이드 문서
https://www.meziantou.net/performance-benefits-of-sealed-class.htm
https://codingcoding.tistory.com/152