부모 클래스에서 자식 클래스로 캐스트 할 수 없습니다.


100

부모 클래스에서 자식 클래스로 캐스트하려고하는데 InvalidCastException이 발생합니다. 자식 클래스에는 int 유형의 속성이 하나만 있습니다. 내가해야 할 일을 아는 사람이 있습니까?


또한 기본 / 파생 관련 클래스에 대해 명시 적 캐스팅을 사용할 수 없다는 것도 알아두면 좋습니다.
Rzassar

답변:


132

C #에서 다운 캐스트하는 간단한 방법은 부모를 직렬화 한 다음 자식으로 역 직렬화하는 것입니다.

 var serializedParent = JsonConvert.SerializeObject(parentInstance); 
 Child c  = JsonConvert.DeserializeObject<Child>(serializedParent);

위의 두 줄의 코드를 사용하여 동물을 개로 캐스팅하는 간단한 콘솔 앱이 있습니다.


22
글쎄, 나는 이것을 "다운 캐스트"라고 부르는 것을 망설 일 것이다.
Kirk Woll

참고로, 변수 이름은 위와 동일하지 않습니다.
Jake Gaston

5
나는 누군가가 상자 밖에서 생각하고 OP에게 할 수 없다고 말하는 사람들을 침묵시키는 것을 좋아합니다 (한두 명의 트롤을 위해 절약하십시오)! 도움을 주셔서 감사합니다. 나는 지난 두 시간 동안 이것을 알아 내려고 노력 해왔다 :)
derekmx271

3
이것은 훌륭한 솔루션입니다. 내 자식 클래스가 추가 기능이없는 부모를위한 래퍼 인 경우가있었습니다. 그렇게했기 때문에 웹 참조가 내 도우미 라이브러리에 있기 때문에 내 애플리케이션으로 가져올 필요가 없었습니다. 이를 통해 부모를 래퍼 클래스로 변환 할 수있었습니다. 감사합니다!
BrianVPS 2017

1
너는 천재 야! :)
Yablargo

121

포유 동물을 개에게 던질 수 없습니다. 고양이 일 수 있습니다.

음식을 샌드위치에 넣을 수는 없습니다. 치즈 버거 일 수 있습니다.

자동차를 Ferrari에 캐스팅 할 수 없습니다. Honda 일 수도 있고, 더 구체적으로 Ferrari 360 Modena를 Ferrari 360 Challange Stradale에 캐스팅 할 수 없습니다. 둘 다 Ferrari 360이지만 다른 부품이 있습니다.


17
이해할 수있는 장애물, 따라서 이러한 방식으로 실제로 '캐스팅'하는 것은 불가능합니다. 하지만 그가 포유류 개체에 안고있는 고양이와 같은 눈 색깔 / 체중 / 머리 모양 / 연령 등의 개를 원한다면 어떻게 될까요? 기본적으로 공통 속성을 복사합니다.
FastAl 2010-06-18

7
FastAl, 이것이 바로 우리가 인터페이스를 갖는 이유입니다. 포유류는 IMammal을 구현해야하며 눈 색깔, 무게 등을 포함해야합니다. 이제 개와 고양이를 모두 IMammal로 캐스팅 할 수 있습니다.
Tom Deloford 2012

1
포유류를 개로 트라이 캐스트 할 수 있습니다. 개라면 개입니다. 그렇지 않으면 null이됩니다. "과부하"함수는 고양이가이를 허용하는 이러한 과부하 함수를 가지고 있다면 고양이에서 개로 불가능한 변환을 가능하게 할 수 있습니다. 그러나 데이터 손실을 처리하고 존재하지 않는 데이터를 조정하는 것은 귀하의 임무입니다. 등, 손톱 발톱을 변환 공을 쫓는로 문자열을 쫓는처럼 ...
TamusJRoyce

나는 예제가 약간 극단적이고 선택적이고 아마도 캐스트가 복사 생성자의 지름길이라고 생각합니다. 예를 들어, 기본 개체 자동차에 정의 된 속성을 사용하여 페라리를 빌드합니다. 또는 인간으로 시작하여 소년을 만드십시오. 캐스팅 및 직접 사용? 아니오 아니오 동의합니다. 그러나 생성자 또는 무언가의 일부인 경우 작동 할 수 있습니다. 아래의 직렬화 답변은 좋은 터치입니다.
sirthomas

1
페라리 비유 NICE
Lord Darth Vader

59

기본 클래스 참조가 참조하는 인스턴스는 자식 클래스의 인스턴스가 아닙니다. 잘못된 것이 없습니다.

더 구체적으로:

Base derivedInstance = new Derived();
Base baseInstance = new Base();

Derived good = (Derived)derivedInstance; // OK
Derived fail = (Derived)baseInstance; // Throws InvalidCastException

캐스트가 성공하려면 다운 캐스팅중인 인스턴스가 다운 캐스팅중인 클래스의 인스턴스 여야합니다 (또는 적어도 다운 캐스팅 할 클래스가 인스턴스의 클래스 계층 내에 있어야 함). 캐스트가 실패합니다.


또는 잠재적으로 Base otherDerived = new OtherDerived (); 파생 된 otherFail = (파생 된) otherDerived;
Blair Conrad

class Base {} class Derived : Base {} // 주 메서드에서 Base derivedInstance = new Derived (); 기본 baseInstance = new Base (); 파생 상품 = (파생) derivedInstance; 파생 실패 = (파생) baseInstance; 이것은 .NET 3.5에서 오류없이 컴파일됩니다. 당신이 말하는 문제는 어디입니까?
pradeeptp

8
@pradeeptp : 물론 빌드됩니다. 컴파일 오류에 대해 누가 말했습니까?
Greg D

18

나는 대부분의 사람들이 명백한 부모-자녀 캐스팅 이 불가능하다고 말하는 것을 보았습니다 . 그것은 사실이 아닙니다. 수정 된 시작을 예로 들어 증명해 보겠습니다.

.net에서 알다시피 모든 주물에는 두 가지 범주가 있습니다.

  1. 값 유형
  2. 참조 유형 (귀하의 경우 참조 유형)

참조 유형에는 시나리오가 속할 수있는 세 가지 주요 상황 사례가 추가로 있습니다.

자식에서 부모로 (암시 적 캐스팅-항상 성공)

사례 1. 직간접 부모의 자녀

Employee e = new Employee();
Person p = (Person)e; //Allowed

부모에서 자식으로 (명시 적 캐스팅-성공할 수 있음)

사례 2. 부모 개체를 보유하는 부모 변수 (허용되지 않음)

Person p = new Person();  // p is true Person object
Employee e = (Employee)p; //Runtime err : InvalidCastException <-------- Yours issue

사례 3. 자식 개체를 보유하는 부모 변수 (항상 성공)

참고 : 객체는 다형성 특성을 갖기 때문에 부모 클래스 유형의 변수가 자식 유형을 보유 할 수 있습니다.

Person p = new Employee(); // p actually is Employee
Employee e = (Employee)p; // Casting allowed

결론 : 무엇보다 먼저 읽은 후 부모에서 자녀로의 전환이 가능한 것처럼 이제 이해가 되길 바랍니다 (Case 3).

질문에 대한 답변 :

귀하의 대답은 case 2입니다. 이러한 캐스팅이 OOP에서 허용되지 않고 OOP의 기본 규칙 중 하나를 위반하려는 경우 항상 안전한 경로를 선택하십시오.

또한 이러한 예외적 인 상황을 피하기 위해 .net은 is / as 연산자를 사용하여 정보에 입각 한 결정을 내리고 안전한 캐스팅을 제공하는 데 도움이 될 것을 권장했습니다 .


17

그러한 캐스트가 의미가있는 경우가 있습니다.
제 경우에는 네트워크를 통해 BASE 클래스를 받고 있었고 더 많은 기능이 필요했습니다. 그래서 내가 원하는 모든 종소리와 휘파람으로 내 편에서 처리하도록 유도하고 수신 된 BASE 클래스를 DERIVED 클래스로 캐스팅하는 것은 단순히 옵션이 아닙니다 (Throws InvalidCastException of Course)

실용적인 생각 중 하나 는 실제로 BASE 클래스를 상속하지 않고 멤버로 포함하는 EXTENSION Helper 클래스를 선언하는 것입니다.

public class BaseExtension
{
   Base baseInstance;

   public FakeDerived(Base b)
   {
      baseInstance = b;
   }

   //Helper methods and extensions to Base class added here
}

당신이 느슨한 결합을 그냥하지 않고 기본 클래스에 추가 기능의 몇 가지가 필요하면 정말 유도의 절대 필요성을 가지고, 그것은 빠르고 간단한 해결 방법이 될 수 있습니다.


비슷한 맥락에서 사용할 수 있도록 BaseExtension최소한 여기에서 구현하기를 원할 것이라고 생각하는 것이 맞 IBase습니까? 아니면 당신의 필요에 중요하지 않았습니까?
tobriand

몇 번 상속에 대한 적절한 대체 될 수 있습니다 포함
바히드 Ghadiri

13

그것은 객체 지향 원칙에 위배됩니다. 여기와 프로젝트의 다른 곳에서 우아한 솔루션은 AutoMapper 와 같은 개체 매핑 프레임 워크 를 사용하여 프로젝션을 구성하는 것입니다.

다음은 필요한 것보다 약간 더 복잡한 구성이지만 대부분의 경우에 충분히 유연합니다.

public class BaseToChildMappingProfile : Profile
{
    public override string ProfileName
    {
        get { return "BaseToChildMappingProfile"; }
    }

    protected override void Configure()
    {
        Mapper.CreateMap<BaseClass, ChildClassOne>();
        Mapper.CreateMap<BaseClass, ChildClassTwo>();
    }
}


public class AutoMapperConfiguration
{
    public static void Configure()
    {
        Mapper.Initialize(x =>
        {
            x.AddProfile<BaseToChildMappingProfile>();
        });
    }
}

응용 프로그램이 호출을 시작하면 AutoMapperConfiguration.Configure()다음과 같이 프로젝션 할 수 있습니다.

ChildClassOne child = Mapper.Map<BaseClass, ChildClassOne>(baseClass);

속성은 규칙에 따라 매핑되므로 클래스가 상속 된 경우 속성 이름이 정확히 동일하고 매핑이 자동으로 구성됩니다. 구성을 조정하여 추가 속성을 추가 할 수 있습니다. 설명서를 참조하십시오 .


Automapper를 사용하여 단일 속성이있는 유형을 다른 유형에 매핑하는 것은 (설명 된 OP처럼) 썰매 망치를 사용하여 달걀을 깨는 것과 같습니다. 파생 된 유형을 새로 작성하고 속성을 직접 할당하지 않는 이유는 무엇입니까 (코드 한 줄).
bytedev

9

Paul, 'Can I do it'을 묻지 않았습니다. 방법 을 알고 싶다고 가정 합니다. 이 그것을 을 !

프로젝트에서이 작업을 수행해야했습니다. 일반적인 방식으로 한 번만 설정 한 많은 클래스가 있으며 파생 된 클래스에 고유 한 속성을 초기화합니다. VB를 사용하므로 샘플이 VB (강인한 noogies)에 있지만 더 나은 C # 버전이있는이 사이트에서 VB 샘플을 훔쳤습니다.

http://www.eggheadcafe.com/tutorials/aspnet/a4264125-fcb0-4757-9d78-ff541dfbcb56/net-reflection--copy-cl.aspx

샘플 코드 :

Imports System
Imports System.Collections.Generic
Imports System.Reflection
Imports System.Text
Imports System.Diagnostics

Module ClassUtils

    Public Sub CopyProperties(ByVal dst As Object, ByVal src As Object)
        Dim srcProperties() As PropertyInfo = src.GetType.GetProperties
        Dim dstType = dst.GetType

        If srcProperties Is Nothing Or dstType.GetProperties Is Nothing Then
            Return
        End If

        For Each srcProperty As PropertyInfo In srcProperties
            Dim dstProperty As PropertyInfo = dstType.GetProperty(srcProperty.Name)

            If dstProperty IsNot Nothing Then
                If dstProperty.PropertyType.IsAssignableFrom(srcProperty.PropertyType) = True Then
                    dstProperty.SetValue(dst, srcProperty.GetValue(src, Nothing), Nothing)
                End If
            End If
        Next
    End Sub
End Module


Module Module1
    Class base_class
        Dim _bval As Integer
        Public Property bval() As Integer
            Get
                Return _bval
            End Get
            Set(ByVal value As Integer)
                _bval = value
            End Set
        End Property
    End Class
    Class derived_class
        Inherits base_class
        Public _dval As Integer
        Public Property dval() As Integer
            Get
                Return _dval
            End Get
            Set(ByVal value As Integer)
                _dval = value
            End Set
        End Property
    End Class
    Sub Main()
        ' NARROWING CONVERSION TEST
        Dim b As New base_class
        b.bval = 10
        Dim d As derived_class
        'd = CType(b, derived_class) ' invalidcast exception 
        'd = DirectCast(b, derived_class) ' invalidcast exception
        'd = TryCast(b, derived_class) ' returns 'nothing' for c
        d = New derived_class
        CopyProperties(d, b)
        d.dval = 20
        Console.WriteLine(b.bval)
        Console.WriteLine(d.bval)
        Console.WriteLine(d.dval)
        Console.ReadLine()
    End Sub
End Module

물론 이것은 실제로 캐스팅이 아닙니다. 새 파생 개체를 만들고 부모에서 속성을 복사하여 자식 속성을 비워 둡니다. 그게 내가해야 할 전부이고 당신이해야 할 전부인 것 같습니다. 클래스의 멤버 (공용 변수)가 아닌 속성 만 복사합니다 (하지만 공용 멤버를 노출하는 것이 부끄러운 경우이를 수행하도록 확장 할 수 있음).

일반적으로 캐스팅은 동일한 객체를 가리키는 2 개의 변수를 생성합니다 (여기 미니 자습서, 나에게 코너 케이스 예외를 던지지 마십시오). 여기에는 상당한 파급 효과가 있습니다 (독자에게 연습)!

물론 나는 왜 랑과 그가 당신을 기본에서 파생 인스턴스로 보내지 않고 다른 방식으로 하는지를 말해야합니다. winforms 텍스트 상자 (파생)의 인스턴스를 가져와 Winforms 컨트롤 유형의 변수에 저장할 수있는 경우를 상상해보십시오. 물론 'control'은 개체를 OK 주변으로 이동할 수 있으며 텍스트 상자에 대한 모든 'controll-y'항목 (예 : top, left, .text 속성)을 처리 할 수 ​​있습니다. 텍스트 상자 특정 항목 (예 : .multiline)은 메모리의 텍스트 상자를 가리키는 'control'유형 변수를 캐스팅하지 않고는 볼 수 없지만 여전히 메모리에 있습니다.

이제 컨트롤이 있고 여기에 텍스트 상자 유형의 변수를 대 / 소문자로 지정하려고한다고 상상해보십시오. 메모리의 컨트롤에 '여러 줄'및 기타 텍스트 상자가 없습니다. 참조를 시도하면 컨트롤이 마술처럼 여러 줄 속성을 증가시키지 않습니다! 속성 (여기서는 실제로 값을 저장하는 멤버 변수처럼 보시면-텍스트 상자 인스턴스의 메모리에 on이 있기 때문에) 존재 해야 합니다. 캐스팅 중이므로 가리키는 대상과 동일한 대상이어야합니다. 따라서 그것은 언어 제한이 아니며, 그러한 방식으로 사례를 제시하는 것은 철학적으로 불가능합니다.


1
이것이 사실 이후의 방법이라는 것을 알고 있지만 "If dstProperty IsNot Nothing"테스트에 "AndAlso dstProperty.CanWrite"를 포함하여 읽기 전용 속성이 아닌지 확인해야합니다.
JamesMLV

@JamesMLV-감사합니다. '사후'
-OP

4

객체의 인스턴스는 자식 클래스의 유형을 사용하여 생성해야합니다. 부모 유형 인스턴스를 자식 유형으로 캐스팅 할 수 없습니다.


2

나에게는 다음과 같이 기본 클래스의 모든 속성 필드를 부모로 복사하는 것으로 충분했습니다.

using System.Reflection;

public static ChildClass Clone(BaseClass b)
{
    ChildClass p = new ChildClass(...);

    // Getting properties of base class

    PropertyInfo[] properties = typeof(BaseClass).GetProperties();

    // Copy all properties to parent class

    foreach (PropertyInfo pi in properties)
    {
        if (pi.CanWrite)
            pi.SetValue(p, pi.GetValue(b, null), null);
    }

    return p;
}

모든 물체에 대한 범용 솔루션은 여기 에서 찾을 수 있습니다.


2

C # 7.0부터 is 키워드 를 사용 하여 다음 을 수행 할 수 있습니다 .

해당 클래스가 정의되면 :

class Base { /* Define base class */ }
class Derived : Base { /* Define derived class */ }

그런 다음 다음과 같이 할 수 있습니다.

void Funtion(Base b)
{
    if (b is Derived d)
    {
        /* Do something with d which is now a variable of type Derived */
    }
}

다음과 같습니다.

void Funtion(Base b)
{
    Defined d;
    if (b is Derived)
    {
        d = (Defined)b;
        /* Do something with d */
    }
}

이제 다음을 호출 할 수 있습니다.

Function(new Derived()); // Will execute code defined in if

만큼 잘

Function(new Base()); // Won't execute code defined in if

이렇게하면 다운 캐스트가 유효하고 예외가 발생하지 않을 것임을 확신 할 수 있습니다!


1

캐스팅의 실제 목적은 같거나 타입이어야 파생 에서 당신이 캐스트를 시도하는 유형 ...

또는 반대 방식으로 나타내려면 캐스트하려는 유형이 객체의 실제 유형과 동일하거나 기본 클래스 여야합니다.

실제 개체가 Baseclass 유형 이면 파생 클래스 유형으로 캐스팅 할 수 없습니다.


1

ServiceStack을 사용하는 사람들을위한 직렬화 접근 방식의 변형 :

var child = baseObject.ConvertTo<ChildType>();

또는 더 자세한 정보 :

var child = baseObject.ToJson().FromJson<ChildType>();

ServiceStack의 직렬화는 매우 빠르며 모든 것이 가능할 수 있지만, 이는 지연 시간이 짧은 전송에서 대규모 변환이나 매우 복잡한 유형을위한 솔루션이 아닙니다. 이는 ServiceStack을 사용하는 모든 사람에게 명백 할 수 있지만 댓글을 예상하여 명확히 할 것이라고 생각했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.