오늘 한 일
- 스파르타 코딩클럽 (~4주차 과제)
- 팀 프로젝트 전투 기능 구현하기
오늘은 팀 프로젝트와 개인 진도를 나아가면서 알게된 사실들을 적어보고자 한다.
부모 클래스와 자식 클래스가 있는데 자식 클래스에 생성자를 달고 부모 클래스 참조 변수에다가 자식 생성자를 사용해 인스턴스를 생성했는데 해당 변수에는 아무런 값(null)이 없는 현상.
코드 예시
class Parent { public string Name { get; private set; } public void MyNameIs() { Console.WriteLine($"내 이름은 {Name}이야!"); } } class Child : Parent { public string Name { get; private set; } public Child(string _name) { Name = _name; } } static void Main(string[] args) { Parent child = new Child("Rtan"); child.MyNameIs(); }
- 의도치 않은 출력
- "Rtan"이 출력되지 않았다.
- 분명 Name이 들어간 건 같지만, Name을 꺼내지 않았다.
- 아마 내가 코딩클럽 강의 부분 중 virtual과 override 내용을 들은 적이 있었는데, 부모 클래스 참조 변수로 자식 클래스의 인스턴스에 접근을 시도하려고 하면 부모 클래스에 있는 필드와 메서드부터 사용한다는 얘기를 들은 것 같다. 그래서 부모의 필드 Name(Program.Parent)의 Null을 꺼내서 저와 같이 출력된 것 같은 모양이다. (강의 내용에서는 아마 virtual override를 활용해서 자식 필드를 먼저 사용할 수 있도록 설정해놓았던 걸로 들었다.)
! 원인을 분석하면서 알게되는 점
- 상속 받은 자식 클래스의 인스턴스를 생성하면 자식의 필드만 생성되는 것이 아니라 상속받은 모든 클래스들의 필드들도 함께 딸려온 채로 생성된다!
- 사진 예시 1 [위] (최상위 클래스 참조 변수로 참조했을 때) 사진 예시 2 [밑] (해당 클래스 참조 변수로 참조했을 때)
- 보면 child 인스턴스에 부모, 할아버지, 할아할아버지의 필드까지 생성이 되어 있다; ( 그로 인해서 상속 간 같은 이름의 필드가 존재하게 되면 호출했을 때 원하는 입력이 나오지 않을 가능성이 큰 것이다! )
1. 자식에 있는 필드를 없애서 부모의 필드가 가려지지 않게 한다. (생성자 때문에 protected를 해줘야한다.)
-> 해결된 사진
- 위와 같이 보면 상속 간 필드가 하나 밖에 없기에 잘못 가져올 걱정을 하지 않아도 된다!
2. virtual override를 활용하여 자식의 필드부터 꺼내게 만든다. (접근 제한자에 유의하자!)
-> 해결된 사진
- 위 로컬을 보면은 비교를 위해 override 되지 않은 Gold를 추가했다. virtual 필드는 자식 필드에 override된 필드가 존재할 경우 부모가 따로 필드를 가지지 않고 자식의 필드만을 저장하고 있는 모습을 볼 수 있다.
3. 다운캐스팅을 활용
- 다운 캐스팅 사용법
Parent parentInstace = new Child(); Child childInstance = parentInstance; // 만약에 자식 클래스의 인스턴스를 참조하고 있지 않다면 에러가 발생한다!
- 다운캐스팅을 활용하면 우선 순위가 바뀌어서 자식의 필드부터 꺼내오게 된다. (하지만 잘못된 예시와 함께 보여주겠다.)
-> 잘못된 예시
- 해당 출력이 잘못된 이유는 Child에는 MyNameIs 메서드가 없기 때문에 무조건 Parent의 메서드를 사용하게 되고, 그로 인해 Parent 클래스에서 자기 자신의 필드를 활용해서 메서드에 사용된 모습이다.
-> 잘 사용된 예시
- 자식에 동일한 메서드를 넣어주면 의도된 출력이 나온다.
이 문제를 해결하고자 할 때는 DownCasting은 적절하지는 않는 것 같다...
virtual override를 사용할 때 걸릴 수 있는 오류
! 가상 메서드가 private이면 재정의할 적절한 메서드를 찾을 수 없다. (CS0115)
public virtual string Name { get; private set; } -> public override string Name { get; private set; }
! 상속된 멤버를 재정의할 때 액세스 한정자를 변경할 수 없다. (CS0507)
public virtual string Name { get; set; } -> public override string Name { get; protected set; }
base()
- 자식 생성자를 작성할 때 옆에 : base()를 활용하면 부모의 생성자를 불러와서 실행 후에 자식 생성자를 실행한다.
class Parent { public string Name { get; protected set; } public int Gold { get; protected set; } public Parent() { Gold = 1000; } } class Child : Parent { public Child(string _name) : base() // base() { Name = _name; } } // 만들고 나서 실행! Child child = new Child("Rtan"); Console.WriteLine(child.Name); Console.WriteLine(child.Gold);
- 출력 결과
- 정확히 순서상 부모 생성자부터 호출 후 자식 생성자를 호출한다는 것은 아래의 사진을 보면 알 수 있다.
- 위 사진에서 알 수 있듯이 부모에도 Name을 할당하는 코드가 있지만, 순서상 뒤인 child의 생성자가 Name을 새로 덮어서 선언하게 된 모습이다.
디버그 과정
- F11을 통해서 들어가면 base() 부분부터 들어가려는 모습을 볼 수 있다.
- 다시 F11을 통해서 들어가면 해당하는 생성자를 찾아서(매개변수가 기준이다!) 실행한다.
(보면 Gold에 1000과 Name에는 Sparta가 할당된 모습이다.)
- 하지만 그 다음 자식 생성자로 인해서 Name이 Rtan으로 다시 할당된 모습이다.
위와 같이 생성자에 base()를 붙여서 인스턴스화할 때 부모 생성자와 자식 생성자를 세트로 호출할 수 있다!
역시 초기 TIL은 프로그래밍 언어의 기반을 다져가는 게 맞는 것 같다. 물론 처음 배우는 사람에서 언어의 기반을 배워본 적 없는 사람에게까지는 무엇이 언어의 기초이고 기반인지 알 수가 없어 많이 방황할 수 있지만, 그런 방황을 하면서 기반이 뭔지 알아가는 과정도 중요한 학습 과정이라고 생각한다.