
객체 지향 프로그래밍에서 '형변환'은 다형성을 구현하는 핵심적인 기술입니다.
이를 통해 코드를 더 유연하게 만들고 재사용성을 높일 수 있죠.
하지만 잘못 사용하면 예기치 않은 오류를 만날 수도 있는 '양날의 검'과 같습니다.
이번 글에서는 상속 관계에서 발생하는 업캐스팅(Upcasting)과
다운캐스팅(Downcasting)의 개념을 알아보고, 어떻게 하면 안전하게
형변환(Type Casting)을 할 수 있는지 is와 as키워드를 통해 살펴보겠습니다!
먼저, 클래스의 형변환 예제를 위해 간단한 동물 클래스들을 준비해 볼게요.
[코드]
using System;
// 부모(기반) 클래스
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("동물이 소리를 냅니다.");
}
}
// 자식(파생) 클래스 1
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("멍멍!");
}
// Dog 클래스만의 고유 메서드
public void FetchBall()
{
Console.WriteLine("공을 물어옵니다!");
}
}
// 자식(파생) 클래스 2
public class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("야옹!");
}
}
업캐스팅은 자식 클래스의 객체를 부모 클래스 타입의 변수에 할당하는 것을 말합니다.
상속 계층도를 기준으로 '아래에서 위로' 올라가는 모양이라 업캐스팅이라고 부릅니다.
[코드]
class Program
{
static void Main()
{
Dog myDog = new Dog(); // 자식 클래스 객체 생성
// 자식(Dog) 객체를 부모(Animal) 타입 변수에 할당 -> 업캐스팅
// 별도의 캐스팅 연산자 없이도 암시적으로(Implicitly) 형변환이 일어납니다.
Animal someAnimal = myDog;
someAnimal.Speak(); // 출력: 멍멍!
// 동물들을 담을 리스트 (다형성 구현)
// Dog, Cat 등 서로 다른 자식 객체들을 Animal이라는 하나의 타입으로 관리할 수 있습니다.
List<Animal> animals = new List<Animal>();
animals.Add(new Dog());
animals.Add(new Cat());
foreach (var animal in animals)
{
animal.Speak(); // 각 객체의 실제 타입에 맞는 Speak()가 호출됨
}
}
}
[실행 결과]
멍멍!
멍멍!
야옹!
업캐스팅된 변수는 부모 클래스에 정의된 멤버에만 접근할 수 있습니다.
비록 실제 객체는 Dog일지라도, someAnimal변수를 통해서는
Dog클래스 고유의 메서드인 FetchBall()을 호출할 수 없습니다.
// someAnimal.FetchBall(); // 컴파일 에러! Animal 타입에는 FetchBall()이 없음
다운캐스팅은 업캐스팅된 객체를 원래의 자식 클래스 타입으로 되돌리는 것을 말합니다.
상속 계층도를 기준으로 '위에서 아래로' 내려오는 모양이라 다운캐스팅이라고 하죠.
다운캐스팅은 명시적(Explicitly)으로 타입을 지정해주어야 하며, 실패할 위험이 있습니다.
[코드]
class Program
{
static void Main()
{
Animal someAnimal = new Dog(); // 업캐스팅된 객체
// 다운캐스팅: (자식타입)으로 명시해야 함
Dog originalDog = (Dog)someAnimal;
originalDog.Speak(); // 멍멍!
originalDog.FetchBall(); // 성공! 이제 Dog의 모든 멤버에 접근 가능
}
}
[실행 결과]
멍멍!
공을 물어옵니다!
만약 부모 타입 변수에 담긴 객체가 변환하려는 자식 타입이 아니라면 어떻게 될까요?
'동물' 상자 안에 '고양이'가 있는데, '강아지'라고 우기며 강제로 꺼내려는 상황과 같습니다.
Animal anotherAnimal = new Cat(); // 실제 객체는 Cat
// 잘못된 다운캐스팅 시도!
// 이 코드는 컴파일은 통과하지만, 실행하는 순간 예외가 발생하며 프로그램이 비정상적으로 종료됩니다.
Dog wrongDog = (Dog)anotherAnimal; // 실행 시 InvalidCastException 예외 발생!
이런 위험한 다운캐스팅을 피하기 위해 C#은 두 가지 안전장치를 제공합니다.
객체가 특정 타입인지 아닌지를 확인하여 true 또는 false를 반환합니다.
예외를 발생시키지 않고 타입을 안전하게 확인할 수 있습니다.
[코드]
class Program
{
static void Main()
{
Animal animalToCheck1 = new Cat();
if (animalToCheck1 is Dog)
{
Dog checkedDog = (Dog)animalToCheck1;
checkedDog.FetchBall();
Console.WriteLine("강아지로 변환 성공!");
}
else
{
Console.WriteLine("이 동물은 강아지가 아니에요.");
}
// C# 7.0 이상에서는 더 간결하게 사용 가능 (패턴 매칭)
Animal animalToCheck2 = new Dog();
if (animalToCheck2 is Dog dog) // is로 확인과 동시에 변수 할당
{
dog.FetchBall();
Console.WriteLine("강아지로 변환 성공!");
}
}
}
[실행 결과]
이 동물은 강아지가 아니에요.
공을 물어옵니다!
강아지로 변환 성공!
먼저 형변환을 시도하고, 성공하면 변환된 객체를 반환하고,
실패하면 예외를 던지는 대신 null을 반환합니다.
[코드]
class Program
{
static void Main()
{
Animal animalToTry = new Cat();
Dog triedDog = animalToTry as Dog;
if (triedDog != null)
{
triedDog.FetchBall();
Console.WriteLine("강아지로 변환 성공!");
}
else
{
Console.WriteLine("변환에 실패하여 null이 되었습니다.");
}
}
}
[실행 결과]
변환에 실패하여 null이 되었습니다.
각 키워드의 역할을 명확히 이해하고 상황에 맞게 사용하는 것이 중요합니다.
| 구분 | 설명 | 특징 |
|---|---|---|
| 업캐스팅 | 자식 → 부모 | 암시적, 항상 안전 |
| 다운캐스팅 | 부모 → 자식 | 명시적, 실패 위험 있음 |
is 연산자 | 타입이 맞는지 확인 | true/false 반환, 안전 |
as 연산자 | 타입을 변환 시도 | 실패 시 null 반환, 안전 |