I'm FanJae.
[20260508] C# (Upcasting, Downcasting, is / as) 본문
1. 캐스팅(Casting)
(1) 정의
- Casting이란 객체나 값을 다른 타입으로 변환해서 바라보는 것을 의미한다.
- 상속 관계에서는 하나의 객체를 부모 타입 또는 자식 타입 관점으로 다룰 수 있게 해준다.
2. 업캐스팅(Upcasting)
(1) 정의
- Upcasting은 자식 객체를 부모 타입으로 참조하는 것을 의미한다.
Dog dog = new Dog();
Animal animal = dog;
- 여기서 animal 변수는 Animal 타입이지만, 자식 객체인 dog 를 참조하고 있다.
- 따라서, Upcasting이 발생한 것이다.
(2) 특징
- Upcasting은 자식 클래스 객체를 부모 클래스 타입으로 참조하는 상향 형변환이다.
- 명시적인 캐스팅을 하지 않아도 자동으로 변환이 가능하다.
- 부모 클래스 타입 관점으로 보는 것이다.
- 즉, 변수의 타입이 부모 클래스이기 때문에, 코드 상에서는 부모 클래스에 정의된 기능만 사용할 수 있다.
- 실제 객체는 여전히 자식 클래스 객체이다.
- 즉, 업캐스팅을 하더라도 객체 자체가 부모 클래스 객체로 바뀌는 것은 아니다.
(3) Upcasting이 필요한 이유
- 여러 자식 객체를 부모 타입 하나로 묶어서 다룰 수 있기 때문이다.
① Upcasting이 필요한 예제 (다형성 구현)
abstract class Animal
{
protected string Name;
public abstract void MakeSound();
}
class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name}이 멍멍! 하고 짖습니다");
}
}
class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name}이 야옹~~~! 하고 웁니다");
}
}
Animal[] animals = { new Dog("개"), new Cat("고양이") };
animals[0].MakeSound();
animals[1].MakeSound();
- 이렇게 부모 타입을 이용해서 자식 객체에 접근할 수 있다.
- 이전 내용부터 몇 번씩 강조되고 있는 다형성을 구현할 수 있게 되는 것이다.
(4) 주의점
- 업캐스팅 상태에서는 부모 클래스에 정의된 멤버만 접근 가능하다.
- 예를들어, Animal 에는 없고, Dog에만 존재하는 Bite() 메소드가 있다고 가정한다.
① Upcasting 상황에서 접근이 불가능한 경우
class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name}이 멍멍! 하고 짖습니다");
}
public void Bite()
{
Console.WirteLine($"{Name}이 당신을 물었습니다.");
}
}
Animal animal = new Dog("개");
animal.MakeSound(); // 가능
animal.Bite() // 불가능
- 업캐스팅 상태에서는 컴파일 타임 기준으로 변수의 타입이 부모 클래스 타입이다.
- 따라서 animal은 부모 클래스에 정의된 멤버(MakeSound())에만 접근할 수 있다.
- 하지만 virtual / override 메서드는 런타임에 실제 객체 타입을 기준으로 실행된다.
3. 다운캐스팅(Downcasting)
(1) 정의
- Downcasting은 부모 타입으로 참조 중인 객체를 다시 자식 타입으로 변환하는 것이다.
Animal animal = new Dog();
Dog dog = (Dog)animal;
- 여기서 animal은 Dog 객체를 참조하고 있지만, 실제 타입은 Animal 이다.
- 다음 문장에서 명시적 캐스팅을 하여, Dog 타입으로 변환하였다.
- 따라서, Downcasting이 발생한 것이다.
(2) 특징
- Downcasting은 부모 타입을 자식 타입으로 변환하는 하향 형변환이다.
- 명시적인 형변환이 필요하다.
- 형변환의 실패 가능성이 존재한다.
- 잘못된 타입으로 캐스팅하면 런타임 예외가 발생한다.
(3) 필요한 이유
- 부모 타입으로는 자식 클래스만 가진 기능에 접근할 수 없기 때문에 필요하다.
① Downcasting이 필요한 예제
class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name}이 멍멍! 하고 짖습니다");
}
public void Bite()
{
Console.WirteLine($"{Name}이 당신을 물었습니다.");
}
}
Animal animal = new Dog("개");
animal.MakeSound(); // 가능
animal.Bite() // 불가능
Dog dog = (Dog)animal;
dog.Bite(); // 가능
- 이렇게 부모 타입을 자식 타입으로 Downcasting하여 자식 클래스만 가진 기능에 접근 가능하다.
- Downcasting은 필요한 경우에만 사용하고, 가능하면 다형성을 활용해 피해야 한다.
② 그러면 어떻게 나눠야 하는가?
- 개의 물기(Bite)를 ‘공통 기능’으로 묶을 수 있는지 생각해야 한다.
- 개의 물기를 ‘공격’이라고 생각해보면, 설계 관점에서 4가지로 나뉠 수 있다.
- Case 1. 모든 동물에 대해서 똑같은 동작을 한다.
→ 부모 클래스에 구현하여 상속 형태로 처리한다.
- Case 2. 똑같은 동작을 할 수 있지만, 일부 동물은 다른 동작을 한다.
→ virtual를 이용한 구현
- Case 3. 모든 동물이 서로 다른 동작을 해야 한다.
→ abstract를 이용한 구현
- Case 4. 어떤 동물은 공격을 할 수 있지만, 어떤 동물은 공격을 할 수 없다.
→ interface를 이용한 구현
(1) Case 1. 모든 동물에 대해서 똑같은 동작을 하는 경우
class Animal
{
protected string Name;
public void Attack()
{
Console.WriteLine($"{Name}이 공격한다.");
}
}
class Dog : Animal
{
}
- 모든 동물이 동일한 동작을 한다면 상속으로 처리해서 부모 클래스에 구현해야 한다.
(2) Case 2. 기본은 똑같지만, 일부 동물만 다른 동작인 경우
- virtual 을 사용한 다형성 구현
- 모든 동물에게 기본 공격 개념(기본 구현)을 두고 싶다면, virtual 형태로 처리한다.
- 이렇게 처리하면, 자식 클래스(각 동물)은 필요하면 override 하여 바꿀 수 있다.
class Animal
{
protected string Name;
public virtual void Attack()
{
Console.WriteLine($"{Name}이 공격한다.");
}
}
class Dog : Animal
{
public virtual void Attack()
{
Console.WriteLine($"{Name}이 이빨로 물어 뜯습니다.");
}
}
(3) Case 3. 모든 동물이 서로 다른 동작을 해야 하는 경우
- abstract 을 사용한 다형성 구현
- 동물에게 기본 공격 개념을 두고 싶지 않다면, abstract 형태로 처리한다.
- 이렇게 처리하면, 자식 클래스(각 동물)은 반드시 override 해야 한다.
abstract class Animal
{
protected string Name;
public abstract void Attack();
}
class Dog : Animal
{
public override void Attack()
{
Console.WriteLine($"{Name}이 이빨로 물어 뜯습니다.");
}
}
(4) Case 4. 모든 동물이 공격을 하지 않는 경우
- 인터페이스를 이용해서 IAttackable . 즉, 공격을 구현하는 형태로 만든다.
interface IAttackable
{
void Attack();
}
class Dog : Animal, IAttackble
{
public void Attack()
{
Console.WriteLine($"{Name}이 이빨로 물어 뜯습니다.");
}
}
(4) 주의점
- Downcasting은 항상 성공하는 변환이 아니다.
- 부모 타입 변수라고 해서 모든 자식 타입으로 변환할 수 있는 것은 아니다.
Animal animal = new Dog();
Dog dog = (Dog)animal; // 가능
Cat cat = (Cat)animal; // 불가능
- 실제 객체가 해당 자식 타입이거나, 해당 자식 타입으로 변환 가능한 경우에만 성공한다.
- 잘못된 타입으로 명시적 캐스팅을 하면 런타임 예외가 발생한다.
- 따라서 안전한 캐스팅이 필요하고, 가능하면 다운 캐스팅 상황을 만들지 않는게 좋다.
4. 안전한 Downcasting
(1) is
- is 는 객체가 특정 타입으로 변환 가능한지 확인한다. 가능하면 바로 변수로 사용할 수 있다.
- 변환이 가능하면, true 실패하면, false 를 반환한다.
- ‘패턴 매칭’과 함께 사용하면, 타입 확인과 동시에 변환된 변수를 바로 사용할 수 있다.
Animal animal = new Dog();
if (animal is Dog dog)
{
dog.Bite();
}
- 위에서 Dog dog 가 패턴 매칭이다. 객체가 특정 타입이나 조건에 맞는지 검사한다.
- 조건에 맞으면 그 값을 해당 타입의 변수로 사용할 수 있게 해준다.
(2) as
- as 는 캐스팅에 성공하면 해당 타입 참조를 반환한다. 실패하면 예외 대신 null 을 반환한다.
Animal animal = new Dog();
Dog dog = animal as Dog;
if (dog != null)
{
dog.Bite();
}
(3) is와 as의 차이
- is 는 타입 확인과 변환을 한 번에 처리할 때 사용하기 좋다.
- as 는 변환을 시도한 뒤, 결과가 null인지 확인하는 방식이다.
- 다운캐스팅이 필요한 상황이면 is 를 이용한 패턴 매칭 방식이 직관적이다.
- as는 변환 결과를 변수에 저장해두고, 이후에도 계속 사용하고 싶을 때 사용한다.
'Unity > Unity 초격차캠프' 카테고리의 다른 글
| [20260507] C# ( namespace ) (0) | 2026.05.07 |
|---|---|
| [20260507] C# ( 추상 클래스 & 인터페이스의 차이점 ) (0) | 2026.05.07 |
| [20260507] C# ( 추상 클래스, 인터페이스 ) (0) | 2026.05.07 |
| [20260506] C# ( 오버로딩 & 오버라이딩 차이점 ) (0) | 2026.05.06 |
| [20260506] C# (캡슐화, 상속, 다형성) (0) | 2026.05.06 |