오늘의 학습 키워드

System.InvalidCastException

 

공부한 내용

System.InvalidCastException 오류

Skill 클래스의 필드인 SkillType에 따라 서브 클래스들을 다운 캐스팅하는 작업을 진행하려는데 System.InvalidCastException 오류가 떴다.

그래서 무엇이 문제인지 확인하기 위해 아래에 보이듯이 테스트용 Skill 리스트를 새로 만들고 데이터를 넣은 후 다운캐스팅을 해봤다.

하지만 이 로직에서는 문제가 없었는데 아래의 multiSkill과 비교했더니 테스트용 Skill은 Action 값이 들어있었고 내가 json데이터로 받아온 Skill은 Action 값이 들어있지 않았다.

그래서 이유를 짐작해보면

1. Json 데이터에 담긴 업캐스팅을 한 서브클래스들은 베이스 클래스인 Skill의 정보만 저장할 수 있고

2. 서브 클래스에만 있는 Action은 저장되지 않는다.

3. 그래서 업캐스팅을 진행하더라도 Json 데이터로 불린 Skill은 서브 클래스에 대한 정보가 없는 것이다.

if (skillNum != 0)
{

    switch (player.Skills[skillNum - 1].Type)
    {
        case SkillType.SigleTarget:
            SingleSkill singleSkill = (SingleSkill)player.Skills[skillNum - 1];
            singleSkill.UseSkill(monsters[targetIndex], damage);
            Console.WriteLine($"{monsters[targetIndex].Name}을(를) 맞췄습니다. {damageMessage}");
            break;
        case SkillType.MultipleTarget:
            List<Skill> skills = new List<Skill>();
            skills.Add(new SingleSkill("라이징 샷", "공격력 * 2.25 로 하나의 적을 공격합니다.", 15, 2.25f, null));
            SingleSkill single = (SingleSkill)skills[0];
            MultipleSkill multiSkill = (MultipleSkill)player.Skills[skillNum - 1];

            // 출력을 스킬에서 처리
            multiSkill.UseSkill(monsters, damageMessage, damage);
            break;
    }
namespace TextRPG_Team
{
    public enum SkillType
    {
        SigleTarget,
        MultipleTarget
    }

    public class SingleSkill : Skill
    {
        public Action<Monster, float, float> SingleAction { get; }

        public SingleSkill(string name, string description, int cost, float damageMod, Action<Monster, float, float> singleAction) 
            : base(name, description, SkillType.SigleTarget, cost, damageMod)
        {
            SingleAction = singleAction;
        }

        public void UseSkill(Monster target, int damage)
        {
            SingleAction?.Invoke(target, damage, DamageMod);
        }
    }

    public class MultipleSkill : Skill
    {
        public int TargetCount { get; }
        public Action<Monster[], string, float, float, int> MultipleAction { get; }

        public MultipleSkill(string name, string description, int cost, float damageMod, Action<Monster[], string, float, float, int> multipleAction, int targetCount = int.MaxValue) 
            : base(name, description, SkillType.MultipleTarget, cost, damageMod)
        {
            TargetCount = targetCount;
            MultipleAction = multipleAction;
        }

        public void UseSkill(Monster[] targets, string damageMessage, int damage)
        {
            MultipleAction?.Invoke(targets, damageMessage, damage, DamageMod, TargetCount);
        }
    }

    public class Skill
    {
        public string Name { get; }
        public SkillType Type { get; }
        public int Cost { get; }
        public float DamageMod { get; }
        public string Description { get; }

        // 단일 타겟 스킬과 다중 타겟 스킬을 생성자로 나눴다.
        
        // 배틀 시스템 들어갔을 때 type에 따라 캐스팅하여 UseSkill 호출하면 됨

        public Skill(string name, string description, SkillType type, int cost, float damageMod)
        {
            Name = name;
            Cost = cost;
            Type = type;
            DamageMod = damageMod;
            Description = description;
        }

    }
}

그래서 아래의 방식으로 해결했다.

그 방식은 서브 클래스에 스킬 메서드를 저장하지 않고 정보만 저장한 뒤 나중에 타입에 따라서 불러오는 것이다. (런타임 중 초기화)

구현해 볼 만한 또 다른 방안으로는 데이터로 인덱스 정보만 저장한 뒤 인덱스로 메서드를 불러오는 방식도 좋을 것 같다.

public class Skill
{
    public string Name { get; }
    public string Description { get; }
    public SkillType Type { get; }
    public int Cost { get; }
    public float DamageMod { get; }
    public int TargetCount { get; }

    // 단일 타겟 스킬과 다중 타겟 스킬을 생성자로 나눴다.
    
    // 배틀 시스템 들어갔을 때 type에 따라 캐스팅하여 UseSkill 호출하면 됨

    // targetCount == 0 은 전체 공격으로 설정
    public Skill(string name, string description, SkillType type, int cost, float damageMod, int targetCount = 1)
    {
        Name = name;
        Cost = cost;
        Type = type;
        DamageMod = damageMod;
        Description = description;
        TargetCount = targetCount;
    }
}

 

오늘의 회고

 오늘은 캐릭터 스킬 적용과 인벤토리, 치명타, 회피 UI 작업을 진행했다. 스킬을 적용하는 부분에 있어서 애를 좀 먹었지만 그래도 어떻게 잘 해결해 나간 것 같다. 다른 팀원과의 작업 중 충돌도 있었는데 인벤토리와 상점의 아이템을 통합 시키고 자신이 보유하고 있는지로 판단할 것인지와 인벤토리의 아이템에서 중복이면은 아이템 갯수를 더 늘리지 않고 Count 변수만 늘릴 것인지 하는 기획적인 의견 대립이었다. 팀원분들과 잘 이야기 해서 작업하기 편하게 인벤토리와 상점은 통합시키고 중복 아이템 개수는 소비 아이템만 Count를 늘리는 방식을 택하게 되었다.

 내일은 추가 구현이나 완성도를 높일 생각이다. 일단 보이는 지점들은 아이템에 있어서 너무 뒤죽박죽으로 정리가 안 되어 있는데 정렬이 좀 필요한 것 같고 겉으로 보이는 줄 바꿈이나 안내창 UI의 완성도를 높일 생각이다. 내일도 파이팅!

 

 

참고 : 

https://stackoverflow.com/questions/5240143/invalidcastexception-unable-to-cast-objects-of-type-base-to-type-subclass

 

InvalidCastException: Unable To Cast Objects of type [base] to type [subclass]

I have a custom CustomMembershipUser that inherits from MembershipUser. public class ConfigMembershipUser : MembershipUser { // custom stuff } I am using Linq-to-SQL to read from a database a...

stackoverflow.com

 

+ Recent posts