재미있어 보여서 조금 실험해봤습니다. 말씀하신 상황만 놓고 보면 대략 다음과 같은 코드를 만든 상황인 것 같은데요,
public class A
{
public static List<(string filter, int count)>? filterList { get; set; }
public virtual void Test() { }
}
public class B : A
{
public override void Test()
=> filterList = new List<(string filter, int count)>() { ("B", 1) };
}
public class C : A
{
public override void Test()
=> filterList = new List<(string filter, int count)>() { ("C", 2) };
}
public class D : A
{
public override void Test()
=> filterList = new List<(string filter, int count)>() { ("D", 3) };
}
그리고 각 클래스의 Test 메서드를 호출한 후 서로 인스턴스가 다르게 할당되는가 확인해봤을 때, A, B, C, D가 모두 같은 인스턴스를 사용하는 것으로 나왔습니다.
static class Program
{
static void Main()
{
A a = new A();
// This method does not allocate filterList instance
a.Test();
$"A != null: {A.filterList != null} | B != null: {B.filterList != null} | C != null: {C.filterList != null}".Dump();
object.ReferenceEquals(A.filterList, B.filterList).Dump();
object.ReferenceEquals(A.filterList, C.filterList).Dump();
object.ReferenceEquals(A.filterList, D.filterList).Dump();
object.ReferenceEquals(B.filterList, A.filterList).Dump();
object.ReferenceEquals(B.filterList, C.filterList).Dump();
object.ReferenceEquals(B.filterList, D.filterList).Dump();
object.ReferenceEquals(C.filterList, A.filterList).Dump();
object.ReferenceEquals(C.filterList, B.filterList).Dump();
object.ReferenceEquals(C.filterList, D.filterList).Dump();
object.ReferenceEquals(D.filterList, A.filterList).Dump();
object.ReferenceEquals(D.filterList, B.filterList).Dump();
object.ReferenceEquals(D.filterList, C.filterList).Dump();
foreach (var i in Enumerable.Range(0, 10).AsParallel())
{
B b = new B();
b.Test();
$"A != null: {A.filterList != null} | B != null: {B.filterList != null} | C != null: {C.filterList != null}".Dump();
object.ReferenceEquals(A.filterList, B.filterList).Dump();
object.ReferenceEquals(A.filterList, C.filterList).Dump();
object.ReferenceEquals(A.filterList, D.filterList).Dump();
object.ReferenceEquals(B.filterList, A.filterList).Dump();
object.ReferenceEquals(B.filterList, C.filterList).Dump();
object.ReferenceEquals(B.filterList, D.filterList).Dump();
object.ReferenceEquals(C.filterList, A.filterList).Dump();
object.ReferenceEquals(C.filterList, B.filterList).Dump();
object.ReferenceEquals(C.filterList, D.filterList).Dump();
object.ReferenceEquals(D.filterList, A.filterList).Dump();
object.ReferenceEquals(D.filterList, B.filterList).Dump();
object.ReferenceEquals(D.filterList, C.filterList).Dump();
// C나 D에 filterList를 할당한 적이 없음에도 이미 할당이 된 상태이며 전부 같은 reference로 나오게 됩니다.
C c = new C();
c.Test();
$"A != null: {A.filterList != null} | B != null: {B.filterList != null} | C != null: {C.filterList != null}".Dump();
object.ReferenceEquals(A.filterList, B.filterList).Dump();
object.ReferenceEquals(A.filterList, C.filterList).Dump();
object.ReferenceEquals(A.filterList, D.filterList).Dump();
object.ReferenceEquals(B.filterList, A.filterList).Dump();
object.ReferenceEquals(B.filterList, C.filterList).Dump();
object.ReferenceEquals(B.filterList, D.filterList).Dump();
object.ReferenceEquals(C.filterList, A.filterList).Dump();
object.ReferenceEquals(C.filterList, B.filterList).Dump();
object.ReferenceEquals(C.filterList, D.filterList).Dump();
object.ReferenceEquals(D.filterList, A.filterList).Dump();
object.ReferenceEquals(D.filterList, B.filterList).Dump();
object.ReferenceEquals(D.filterList, C.filterList).Dump();
}
}
}
알아차리기 어렵고 미묘한 부분이긴 한데, 결국 static 필드나 속성은 현재 선언된 클래스 뿐만 아니라, 자식 클래스에 대해서까지도 유일성이 보장되는 특성이 있는 것으로 보입니다. 그래서 말씀해주신대로 static을 지우면 매번 인스턴스화 대상이 되므로 결과가 달라지게 되는 것이겠고요!
잘 알고 쓰는 것이라면 괜찮겠지만, 잘못 이해하고 쓰면 큰 부작용을 만들 수 있는 코드가 되겠습니다.
혹시 잘못 접근한 부분이 있을 경우 피드백 주시면 감사하겠습니다!