어느 경우에도 정적 메서드를 재정의할 수는 없습니다.
이유는 정말 간단합니다. "재정의(Override)"라는 것은 주로 어느 클래스를 상속하는 클래스에서 기능을 변경하거나 추가하기 위해서 주로 사용됩니다.
예로 System.Exception 클래스를 보도록 하겠습니다.
public class Program {
class Secret : Exception {
public Secret(string message) : base(message) { }
public override string Message {
get {
return "안알랴줌";
}
}
}
public static void Main() {
Secret s = new Secret("예외 메세지");
Console.WriteLine(s.Message);
}
}
Secret 이라는 클래스는 System.Exception 클래스를 상속하고 있습니다.
System.Exception(링크를 누르면 소스 코드를 보실 수 있습니다) 클래스의 경우 총 4개의 생성자가 있는 것을 보실 수 있습니다.
만약 위의 클래스에서 Message 속성을 오버라이딩하지 않았다면, s.Message 속성 값은 "예외 메세지"가 되어야 하며 출력 값 또한 동일해야 합니다.
하지만, Message 속성을 오버라이딩했기 때문에 s.Message 속성 값과 출력 값은 "안알랴줌" 이 나오게 됩니다.
여기서 아실 수 있는 것은 Secret.Message 속성은 Secret 클래스가 개체(Instance)화 되었을 때 사용할 수 있다는 것을 아실 수 있습니다.
즉, Secret.Message 속성은 Secret 클래스에 속해있는 것이라고 표현할 수 있습니다.
하지만, 정적 멤버나 정적 메서드의 경우는 그렇지 않습니다.
클래스가 개체화되던 개체화되지 않던 사용할 수 있기 때문이죠.
public class Program {
class StaticMessage {
public StaticMessage() { }
public static string Message() {
return "정적 메서드";
}
}
public static void Main() {
StaticMessage sm = new StaticMessage();
Console.WriteLine(sm.Message());
Console.WriteLine(StaticMessage.Message());
}
}
정적 메서드나 필드의 경우 개체화된 클래스를 통해 접근/호출하는 것이 불가능합니다.
Console.WriteLine(sm.Message()); < 이 부분에서 오류가 나게 되죠.
그 다음 코드를 보시면 Console.WriteLine(StaticMessage.Message()); 처럼 형식_이름.정적_메서드() 형식으로 호출하고 있는 것을 보실 수 있습니다.
여기서는 개체화된 클래스를 통해 접근하는 것을 볼 수 없었습니다.
딱 한 마디로 문제를 해결한다면
Instance 필드는 개체화된 클래스에서만 사용이 가능하지만, Static 필드의 경우 개체화된/되지 않은 클래스에서 사용이 가능합니다.
class Test {
static int Age = 24;
int InstanceAge = 0;
public static void PrintAge() {
Console.WriteLine(Age);
Console.WriteLine(InstanceAge); // ERROR, CANNOT ACCESS INSTANCE FIELD.
}
public void PrintAgeInstance() {
Console.WriteLine(Age);
Console.WriteLine(InstanceAge); // WORKING FINE
}
}
public class Program {
public static void Main() {
Test t = new Test();
Test.PrintAge();
t.PrintAgeInstance();
}
}
이런 결과가 나오는 이유는 딱 한가지입니다.
정적인 메서드, 필드 등은 개체화를 통하지 않아도(열린 문) 접근이 가능합니다.
인스턴스 메서드, 필드 등은 개체화를 통해야(닫힌 문, 개체화함으로 문을 염) 접근이 가능합니다.
왜 이런 이야기를 갑자기 하느냐..
제가 프로그래밍 도중 큰 문제에 직면했습니다.
파일 형식에서 사용되는 헤더가 있는데, 기능이나 필드를 추가한 상위 버전 헤더를 사용자가 작성할 수 있게 해야 했습니다.
그리고, 기초 모델이 되는 헤더 형식에는 정적 메서드인 FromStream, FromFile 이 정의되어 있습니다.
헤더는 개체화를 해야만 내부 멤버에 접근이 가능한데, FromStream 메서드는 정적이기 때문에 접근이 불가능합니다.
그런데 이 메서드를 재정의하지 않으면 필드가 추가된 상위 버전의 헤더 필드 값을 읽거나 쓸 수 없어집니다.
그럼 이 문제를 어떻게 해결할까요??..
답은 정말 간단했습니다. 이 문제때문에 몇 시간을 생각했었는데 해결하고 나니 정말 뿌듯합니다.
FromStream 메서드는 정적인 채로 놔두고, 메서드 내부에서 개체를 생성합니다.
그리고 기초 모델이 되는 헤더에 인스턴스 메서드(protected virtual void UserProcessRoutine)를 만들고, 상위 버전의 헤더에서 저 메서드를 재정의하도록 했습니다.
using System.IO;
using System.Reflection;
class HighLevelHeader : BasicHeader {
int g_age;
public HighLevelHeader() : base(0) {
g_age = 0;
}
protected override void UserProcessRoutine(BinaryReader br) {
// 나이를 읽어온다.
g_age = br.ReadInt32();
}
public int Age {
get { return g_age; }
}
}
class BasicHeader {
int g_id;
BasicHeader() { }
protected BasicHeader(int id) {
g_id = id;
}
protected virtual void UserProcessRoutine(BinaryReader br) { }
// 제네릭 형식 T에는 BasicHeader 및 해당 클래스를 상속하는 형식만 사용할 수 있도록
// where 조건을 이용합니다.
public static T FromStream<T>(Stream s) where T : BasicHeader {
// 헤더를 개체화합니다.
// 원래는 Activator.CreateInstance<T>(); 를 사용했는데
// 이 메서드를 사용하면 생성자가 public이 아닐 경우에는 예외가 발생하게 된다.
// 그래서 Reflection을 이용하여 생성자를 호출한다.
// 공용/비공용 생성자를 검색한다.
ConstructorInfo ctor = typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
// 이건 발생하면 절대절대 안된다.
if ( ctor == null ) return default(T);
T header = (T) ctor.Invoke(null);
// 스트림의 데이터를 읽어올 개체를 만들고
BinaryReader br = new BinaryReader(s);
// 값을 읽어옵니다.
header.g_id = br.ReadInt32();
// 여기는 상속 클래스에서 재정의한 메서드가 호출될 곳
header.UserProcessRoutine(br);
// 이제 헤더를 불러왔으니 반환한다.
return header;
}
public int Id {
get { return g_id; }
}
}
public class Program {
public static void Main() {
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
// id = 32
bw.Write(32);
// age = 24
bw.Write(24);
// 헤더 기본 모델을 읽어온다.
ms.Position = 0;
BasicHeader bh = BasicHeader.FromStream<BasicHeader>(ms);
ms.Position = 0;
// 상위 레벨 헤더를 읽어온다.
HighLevelHeader hlh = BasicHeader.FromStream<HighLevelHeader>(ms);
Console.WriteLine(bh.Id);
Console.WriteLine(hlh.Id + "\t" + hlh.Age);
Console.ReadKey(true);
}
}
.NET Fiddle에서는 시간 초과 오류가 나오네요.. VS, SharpDevelop으로는 결과가 잘 나옵니다.
32
32 24
이렇게요!
프로그래밍을 하다가 막히면 그 문제를 해결하기 위해 정말 많은 생각을 합니다. 새로운 관점으로 바라볼 때도 있죠!
그리고 문제가 해결된 순간! 정말 기쁩니다.
이 글이 비슷한 문제를 겪고 있는 분께 도움이 되었으면 좋겠습니다.
'Trouble Shooting' 카테고리의 다른 글
대칭 알고리즘(SymmetricAlgorithm)에서 잘못된 암호가 입력된 것을 어떻게 확인할까? (1) | 2016.04.11 |
---|