부모 클래스에서 자식 클래스로 캐스트하려고하는데 InvalidCastException이 발생합니다. 자식 클래스에는 int 유형의 속성이 하나만 있습니다. 내가해야 할 일을 아는 사람이 있습니까?
답변:
C #에서 다운 캐스트하는 간단한 방법은 부모를 직렬화 한 다음 자식으로 역 직렬화하는 것입니다.
var serializedParent = JsonConvert.SerializeObject(parentInstance);
Child c = JsonConvert.DeserializeObject<Child>(serializedParent);
위의 두 줄의 코드를 사용하여 동물을 개로 캐스팅하는 간단한 콘솔 앱이 있습니다.
포유 동물을 개에게 던질 수 없습니다. 고양이 일 수 있습니다.
음식을 샌드위치에 넣을 수는 없습니다. 치즈 버거 일 수 있습니다.
자동차를 Ferrari에 캐스팅 할 수 없습니다. Honda 일 수도 있고, 더 구체적으로 Ferrari 360 Modena를 Ferrari 360 Challange Stradale에 캐스팅 할 수 없습니다. 둘 다 Ferrari 360이지만 다른 부품이 있습니다.
기본 클래스 참조가 참조하는 인스턴스는 자식 클래스의 인스턴스가 아닙니다. 잘못된 것이 없습니다.
더 구체적으로:
Base derivedInstance = new Derived();
Base baseInstance = new Base();
Derived good = (Derived)derivedInstance; // OK
Derived fail = (Derived)baseInstance; // Throws InvalidCastException
캐스트가 성공하려면 다운 캐스팅중인 인스턴스가 다운 캐스팅중인 클래스의 인스턴스 여야합니다 (또는 적어도 다운 캐스팅 할 클래스가 인스턴스의 클래스 계층 내에 있어야 함). 캐스트가 실패합니다.
나는 대부분의 사람들이 명백한 부모-자녀 캐스팅 이 불가능하다고 말하는 것을 보았습니다 . 그것은 사실이 아닙니다. 수정 된 시작을 예로 들어 증명해 보겠습니다.
.net에서 알다시피 모든 주물에는 두 가지 범주가 있습니다.
참조 유형에는 시나리오가 속할 수있는 세 가지 주요 상황 사례가 추가로 있습니다.
사례 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 연산자를 사용하여 정보에 입각 한 결정을 내리고 안전한 캐스팅을 제공하는 데 도움이 될 것을 권장했습니다 .
그러한 캐스트가 의미가있는 경우가 있습니다.
제 경우에는 네트워크를 통해 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습니까? 아니면 당신의 필요에 중요하지 않았습니까?
그것은 객체 지향 원칙에 위배됩니다. 여기와 프로젝트의 다른 곳에서 우아한 솔루션은 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);
속성은 규칙에 따라 매핑되므로 클래스가 상속 된 경우 속성 이름이 정확히 동일하고 매핑이 자동으로 구성됩니다. 구성을 조정하여 추가 속성을 추가 할 수 있습니다. 설명서를 참조하십시오 .
Paul, 'Can I do it'을 묻지 않았습니다. 방법 을 알고 싶다고 가정 합니다. 이 그것을 을 !
프로젝트에서이 작업을 수행해야했습니다. 일반적인 방식으로 한 번만 설정 한 많은 클래스가 있으며 파생 된 클래스에 고유 한 속성을 초기화합니다. VB를 사용하므로 샘플이 VB (강인한 noogies)에 있지만 더 나은 C # 버전이있는이 사이트에서 VB 샘플을 훔쳤습니다.
샘플 코드 :
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이 있기 때문에) 이 존재 해야 합니다. 캐스팅 중이므로 가리키는 대상과 동일한 대상이어야합니다. 따라서 그것은 언어 제한이 아니며, 그러한 방식으로 사례를 제시하는 것은 철학적으로 불가능합니다.
나에게는 다음과 같이 기본 클래스의 모든 속성 필드를 부모로 복사하는 것으로 충분했습니다.
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;
}
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
이렇게하면 다운 캐스트가 유효하고 예외가 발생하지 않을 것임을 확신 할 수 있습니다!
ServiceStack을 사용하는 사람들을위한 직렬화 접근 방식의 변형 :
var child = baseObject.ConvertTo<ChildType>();
또는 더 자세한 정보 :
var child = baseObject.ToJson().FromJson<ChildType>();
ServiceStack의 직렬화는 매우 빠르며 모든 것이 가능할 수 있지만, 이는 지연 시간이 짧은 전송에서 대규모 변환이나 매우 복잡한 유형을위한 솔루션이 아닙니다. 이는 ServiceStack을 사용하는 모든 사람에게 명백 할 수 있지만 댓글을 예상하여 명확히 할 것이라고 생각했습니다.