我正在尝试从父类转换为子类,但是却收到InvalidCastException。子类只有一个类型为int的属性。有人知道我需要做什么吗?
Answers:
在C#中向下转换的一种简单方法是序列化父级,然后反序列化到子级中。
var serializedParent = JsonConvert.SerializeObject(parentInstance);
Child c = JsonConvert.DeserializeObject<Child>(serializedParent);
我有一个简单的控制台应用程序,可以在这里使用上面的两行代码将动物放进狗里
您不能将哺乳动物抛入狗中-可能是猫。
您不能将食物倒入三明治中-可能是芝士汉堡。
您无法将汽车投放到法拉利中-可能是本田,或者更确切地说,您无法将法拉利360摩德纳投放到法拉利360 Challange Stradale中-尽管它们都是法拉利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
结论:读完所有文章之后,希望它现在像有意义的父级到子级转换(案例3)一样有意义。
回答问题:
您的答案是在情况2中。在这里您可以看到OOP不允许这种强制转换,并且您试图违反OOP的基本规则之一。因此,请始终选择安全的路径。
此外,为避免此类特殊情况,.net建议使用is / as运算符,它们将帮助您做出明智的决定并提供安全的转换。
在某些情况下,这样的转换很有意义。
以我为例,我正在通过网络接收BASE类,因此我需要更多功能。因此,派生它以我想要的所有花哨功能来处理它,并将接收到的BASE类转换为DERIVED根本不是一种选择(抛出InvalidCastException当然)
一个实际的智囊团,出的现成的解决办法是宣布并没有继承基类实际上是一个扩展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);
属性按照约定进行映射,因此,如果继承了该类,则属性名称完全相同,并且映射是自动配置的。您可以通过调整配置来添加其他属性。请参阅文档。
保罗,您没有问'我能做'-我假设您想知道如何做!
我们必须在一个项目上执行此操作-我们只用一次通用方法设置了许多类,然后初始化了派生类的特定属性。我使用VB,所以我的示例在VB中(艰难的实践),但是我从该站点偷了VB示例,该站点也具有更好的C#版本:
样例代码:
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控件类型的变量中。当然,“控件”可以使对象在“确定”附近移动,并且您可以处理与文本框有关的所有“控件”问题(例如,top,left,.text属性)。如果不将指向文本框的“ control”类型变量转换为指向内存中的文本框,则无法看到特定于文本框的内容(例如,.multiline),但仍在内存中。
现在想象一下,您有一个控件,并且想要将文本框类型的变量设置为大小写。内存中的控件缺少“多行”和其他文本框内容。如果您尝试引用它们,则该控件不会神奇地增加多行属性!属性(看看它像一个成员变量在这里,实际存储的值-因为在文本框实例的内存)必须存在。记住,既然您正在投射,它必须是您指向的对象。因此,这不是语言上的限制,从哲学上讲,以这种方式进行案例化是不可能的。
对我来说,将所有属性字段从基类复制到父级就足够了,如下所示:
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
这样,您可以确定您的垂头丧气将是有效的,并且不会引发异常!