공부/인프런 - Rookiss

★ Part 1-4-4. 객체지향 : 클래스 형변환 (is, as)

셩잇님 2023. 8. 2. 15:53
반응형

 

 

클래스 형식 변환

 

[부모 자식간 형식 변환]

 

Knight knight = new Knight();
 

 

knight 스택 메모리는 new Knight()로 생성한 힙 메모리 객체의 주소를 담는다.

 

// 부모 👉 Plyaer
// 자식 👉 Knight, Maze

    class Player
    {
        public int hp;
        public int attack;
    }
    class Knight : Player
    {
        public void Move() { Console.Write("Kight Move"); }
        public void Attack() { Console.Write("Kight Attack"); }
    }

    class Mage : Player
    {
        public void Move() { Console.Write("Maze Move"); }
        public void Attack() { Console.Write("Maze Attack"); }
    }

 

[업 캐스팅 : 자식 👉 부모 ⭕]

  • 자식(Knight)타입의 객체(new Knight();)를 부모 타입의 변수(player)로 참조하는 것 가능하다.
  • 단 자식 타입의 전부를 호출할 순 없고, 부모로부터 상속 받은 멤버들만 호출이 가능하다. 자식 객체를 참조하고 있더라도 부모 변수로 상속 받지 않은 자식만의 멤버를 호출하려 하면 컴파일 에러를 발생시킨다.

 

Knight knight = new Knight();
Player player = knight; // ⭕ 문제없다.

player.hp = 10; // ⭕ 문제없다.
player.Move();  // ❌ 컴파일 에러!

 

[다운 캐스팅 : 부모 👉 자식 (경우에 따라 다름)]

 

묵시적 형변환을 해주지 않는다. (컴파일 에러)

  • 자식타입의 객체(new Knight();)를 참조하던 부모 타입 변수(player)를 다시 자식 타입으로 형변환 해주는 것이 가능하다. 그러나 컴파일러는 이를 자동 형변환 해주지 않는다. 컴파일러 타임엔 부모 타입 변수인 player가 어떤 타입의 객체를 가리키고 있는지 알 수 없기 때문이다. 
  • 따라서 객체가 메모리를 할당 받는 일은 해당 코드가 실행되는 런타임이기 때문에 컴파일 타임에선 player가 어떤 타입의 객체를 가리키고 있는지 알 수 없서 자동적으로 형변환을 해주지 않는다. 따라서 아래와 같이 개발자가 직접 명시적으로 형변환을 해주어야 한다.

 

Player player = new Knight();
Knight knight = player;  // ❌ 컴파일 에러! 자동 형변환 해주지 않음.
Knight knight = (Knight)player;  // ⭕ 이렇게 명시적 형변환을 해주어야 한다.

 

명시적 형변환은 컴파일에선 문제 없으나 경우에 따라 런타임 에러가 발생할 수도 있다.

  • 실제로 형변환이 가능한지는 프로그램을 실행해 봐야 알 수 있다. 객체가 메모리를 할당 받는 일은 해당 코드가 실행되는 런타임이기 때문이다.

 

⭕ 아래의 경우엔 런타임 에러가 발생하지 않는다. 문제 없다.

  • 다시 원래 타입의 객체로 돌려주는 일이기 때문이다. 부모 타입 변수 player는 자식 타입인 Knight 타입 객체를 참조하고 있었기 때문에 player 변수를 Knight 타입으로 형변환 해주는 것이 문제 되지 않는다. 안그래도 Knight 타입 객체를 참조있었기 때문에!

 

Player player = new Knight();
Knight knight = (Knight)player;  // ⭕ 문제 없다.

 

❌ 아래의 경우엔 런타임 에러가 발생한다.

  • 부모 타입 변수 player는 자식 타입인 Knight 타입 객체를 참조하고 있었기 때문에 Maze타입으로 형변환 될 수 없다. 이게 컴파일 타임에선 player가 어떤 객체를 가리키고 있는지 알 수 없고 런타임 때 해당 코드를 실행해 봐야 알 수 있는 부분인데, Knight과 Maze는 둘 다 Player 자식이긴 하지만 Knight타입의 객체는 Maze에만 정의되어 있는 Maze의 멤버들을 담고 있지 않기 때문이다. 
  • 따라서 Maze 타입의 변수 mage로 Knight타입의 객체를 참조할 수 없기 때문에, Knight타입의 객체를 가리키고 있는 player는 Mage로 형변환 될 수 없다.

 

Player player = new Knight();
Mage mage = (Mage)player;  // ❌ 런타임 에러 발생!

Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'CSharp.Knight' to type 'CSharp.Mage'.

 


 

다운 캐스팅시 런타임 에러 방지 문법

 

[is]

A is B 👉 A 변수가 B 타입의 객체를 참조하고 있다면 true 리턴, 아닐경우 false 리턴.

Player player = new Knight();

bool isMage = (player is Mage)
if (isMage)
    Mage mage = (Mage)player;

 

 player는 Knight 타입의 객체를 참조하고 있기 때문에 (player is Mage)의 결과는 False 이다. 따라서 Mage mage = (Mage)player; 이 부분이 실행되지 않기 때문에 런타임 에러를 방지할 수 있다.

 

[as]

A as B 👉 A 변수를 B 타입으로 형변환 하는 것이 가능하다면 형변환을 진행하고 그 결과를 리턴한다. 불가능하다면 null을 리턴한다.

Player player = new Mage();

Mage mage = (player as Mage)
if (mage != null)
    Mage mage = (Mage)player;

 

 player는 Mage 타입의 객체를 참조하고 있기 때문에 Mage로 형변환이 가능하다. 따라서 mage엔 player가 참조하고 있던 객체가 리턴되고 mage와 player는 힙메모리에 있는 동일한 객체를 가리키게 된다. mage는 player가 Mage로 형변환된 결과다. 따라서 Mage mage = (Mage)player; 이 부분이 실행된다. 형변환이 불가능 했다면 이 부분은 실행되지 않았을 것이다.

 

 

 

반응형