수업 시간에 노트에 메모한 것 (오버라이드와 형변환)
개요
상속으로 물려받은 메서드를 자식이 그대로 쓰지 않고 자기 방식으로 다시 정의하는 것을 오버라이드라고 한다.
그리고 부모 타입 변수 하나로 여러 자식을 다루면서 각자의 동작이 나오게 하는 것을 다형성이라고 한다.
오버로드와 오버라이드의 차이점 설명 ( 면접 단골 질문 )
| 구분 | 오버로드 | 오버라이드 |
|---|---|---|
| 무엇이 여럿인가 | 같은 이름 · 다른 매개변수 | 같은 이름 · 같은 매개변수 |
| 어디에 있는가 | 같은 클래스 또는 상속 관계의 메서드들 | 부모와 자식에 나뉘어 있음 |
| 고르는 시점 | 컴파일할 때 인수를 보고 | 실행할 때 객체를 보고 |
| 표기 | 없음 | virtual · override |
모든 클래스의 부모 object
- C#의 모든 클래스는 아무것도 적지 않아도
object를 물려받는다. - 그래서 새로 만든 클래스도 처음부터 메서드 몇 개를 가지고 있다.
GetType()으로 실제 타입 보기
GetType()은 변수에 적힌 타입이 아니라 실제로 만들어진 객체의 타입을 돌려준다.
int number = 1234;
Console.WriteLine(number.GetType());
System.Int32
업캐스팅과 다운캐스팅 ( 캐스팅 = 형변환 )
- 자식은 부모의 한 종류이므로, 부모 타입 변수에 자식 객체를 그대로 담을 수가 있다. 이것을 업캐스팅이라고 한다.
- 업캐스팅은 암시적으로 가능하다.
다시 자식 타입으로 꺼내기
- 부모 타입 변수를 자식 타입으로 되돌리는 것을 다운캐스팅이라고 한다.
- 다운캐스팅은 명시적으로 가능하다.
⚠️주의: 다운캐스팅 실패는 컴파일할 때 걸리지 않고 실행 중에 프로그램을 멈춤. 캐스팅하기 전에 타입을 확인하는 습관이 필요함
is로 타입 확인하기
is연산자는 왼쪽 객체가 오른쪽 타입인지true/false로 알려준다. (bool형)is뒤에 변수 이름을 붙이면 확인과 캐스팅을 한 번에 할 수 있다. 확인에 성공했을 때만 그 변수에 값이 담긴다.
Animal a1 = new Dog();
a1.Name = "초코";
Animal a2 = new Cat();
a2.Name = "나비";
Describe(a1);
Describe(a2);
static void Describe(Animal animal)
{
if (animal is Dog dog)
{
dog.Bark();
}
else if (animal is Cat cat)
{
cat.Meow();
}
}
as로 안전하게 캐스팅하기
as는 캐스팅에 실패하면 프로그램을 멈추는 대신null을 돌려준다.
Animal a1 = new dog();
static void TryBark(Animal animal)
{
Dog dog = animal as Dog;
if (dog != null)
{
dog.Bark();
}
else
{
Console.WriteLine($"{animal.Name}: 짖을 수 없음");
}
}
| 방법 | 실패했을 때 |
|---|---|
(Dog)animal | 프로그램이 멈춤 |
animal is Dog | false |
animal is Dog dog | false, 변수는 채워지지 않음 |
animal as Dog | null |
메서드 오버라이드
- 자식마다 다르게 동작시키기
- 업캐스팅을 해 두면 부모가 가진 멤버만 쓸 수 있다. 그런데 멤버가 자식마다 다르게 동작한다면 부모 타입 하나로 모두를 다룰 수 있게 된다.
- 사용하기 위해선 부모 쪽에
virtual자식 쪽에override를 붙인다.
| 키워드 | 붙이는 곳 | 뜻 |
|---|---|---|
virtual | 부모 메서드 | 자식이 다시 정의해도 됨 |
override | 자식 메서드 | 부모의 그 메서드를 다시 정의함 |
virtual이 없으면
- 부모에
virtual이 없는데 자식이override하려 하면 컴파일이 되지 않는다.
base.로 부모 것을 함께 쓰기
- 다시 정의하되 부모가 하던 일을 버리고 싶지 않을 때가 존재한다.
base.메서드이름()으로 부모의 원래 메서드를 호출 할 수 있다.
여러 단계에 걸친 오버라이드
override한 메서드는 그 자식이 다시override할 수 있다.
다형성
- 오버라이드와 업캐스팅을 합치면, 서로 다른 자식들을 한 배열에 담아 같은 코드로 다룰 수 있다.
- 다향성의 장점은 자식이 늘어날 때 드러난다.
new로 숨기기
- 다시 정의하는 것이 아니라 가리는 것
virtual·override없이 자식에 같은 이름의 메서드를 두면, 부모의 것을 가리는 것이 된다.
의도한 것임을 밝히려면new를 붙인다.
| 구분 | new (숨기기) | override (재정의) |
|---|---|---|
| 부모 쪽 표기 | 필요 없음 | 재정의 가능한 virtual·abstract·override |
| 자식 쪽 표기 | new | override |
| 자식 타입으로 호출 | 자식 것 | 자식 것 |
| 부모 타입으로 호출 | 부모 것 | 자식 것 |
| 다형성 | 안 됨 | 됨 |
추상 클래스 (abstract)
- 기본 동작이 마땅치 않을 때 사용하며
abstract를 쓰면 본문 없이 이름만 정하고 자식에게 구현을 강제할 수 있다. - 추상 클래스에는 본문 있는 메서드와 없는 메서드를 함께 둘 수 있고 필드와 생성자도 그대로 둘 수 있다.
- 추상 클래스는
new할 수 없다.
virtual과 abstract의 차이
| 구분 | virtual | abstract |
|---|---|---|
| 부모의 본문 | 있음 | 없음 |
| 자식의 재정의 | 선택 | 필수 |
| 클래스 선언 | 보통 클래스 | abstract class 여야 함 |
| 언제 쓰는가 | 쓸 만한 기본 동작이 있음 | 자식마다 반드시 달라야 함 |
sealed override
override한 메서드에sealed를 붙이면 그 아래 자식이 더 이상 다시 정의하지 못한다.- 클래스에 붙이는
sealed는 상속 자체를 막고,selad override는 메서드 하나의 재정의만 막는다.
ToString() 재정의
Console.WriteLine()에 객체를 넘기면 그 객체의ToString()이 불린다.
재정의하지 않았으면object의 기본 동작대로 타입 이름이 나옴
using System;
Person person = new Person("홍길동", 30);
Console.WriteLine(person);
class Person
{
private string name;
private int age;
public Person(string name, int age)
{
this.name = name;
this.age = age;
}
}
Person
ToString()은object에virtual로 선언되어 있으므로override할 수 있다.
Equals 와 GetHashCode
- object의 Equals()는 기본적으로 같은 객체인지를 본다.
필드 값이 모두 같아도 따로 만든 객체면 다르다고 답한다.
using System;
Point p1 = new Point(3, 4);
Point p2 = new Point(3, 4);
Point p3 = p1;
Console.WriteLine($"p1.Equals(p2): {p1.Equals(p2)}");
Console.WriteLine($"p1.Equals(p3): {p1.Equals(p3)}");
Console.WriteLine($"p1 == p2: {p1 == p2}");
class Point
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}
p1.Equals(p2): False
p1.Equals(p3): True
p1 == p2: False
⚠️ 주의: Equals()를 재정의하면서 GetHashCode()를 두지 않으면 경고 CS0659가 남
오늘의 수업에선 GetHashCode는 배우지 않았다.
수업에 잘 따라 가다가 프로퍼티, 상속, 오버라이드와 다형성에서 한 번 또 막혔다.
하지만 포기 하지 않고 꾸준히 공부할 것이다!



