无法从父级转换为子级


100

我正在尝试从父类转换为子类,但是却收到InvalidCastException。子类只有一个类型为int的属性。有人知道我需要做什么吗?


还要知道,您不能对基/派生相关类使用显式强制转换。
Rzassar

Answers:


132

在C#中向下转换的一种简单方法是序列化父级,然后反序列化到子级中。

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

我有一个简单的控制台应用程序,可以在这里使用上面的两行代码将动物放进狗里


22
好吧,我会毫不犹豫地称其为“垂头丧气”。
Kirk Woll

请注意,变量名称与上面的名称不同。
杰克·加斯顿

5
我喜欢有人在开箱即用的情况下思考,并让告诉OP不能完成的人沉默(保存一两个巨魔)!感谢您对此的协助。在过去的几个小时里,我一直在试图解决这个问题:)
derekmx271

3
这是一个很好的解决方案。我遇到过这样的情况,我的子类只是父类的包装器,没有其他功能。我这样做是为了不必将Web参考导入到我的应用程序中,因为它位于我的帮助程序库中。这使我可以将父级转换为包装类。谢谢!
BrianVPS

1
你是个天才!:)
Yablargo

121

您不能将哺乳动物抛入狗中-可能是猫。

您不能将食物倒入三明治中-可能是芝士汉堡。

您无法将汽车投放到法拉利中-可能是本田,或者更确切地说,您无法将法拉利360摩德纳投放到法拉利360 Challange Stradale中-尽管它们都是法拉利360,但部件却有所不同。


17
可以理解的障碍,因此不可能以这种方式实际“投射”。但是,如果他想要一只与被困在哺乳动物物体中的猫的眼睛颜色/体重/发型/年龄等相同的狗怎么办?本质上是复制通用属性。
FastAl 2010年

7
FastAl,这正是我们拥有接口的原因。哺乳动物必须实现IMammal,并且必须包含眼睛的颜色,体重等。现在,您可以将猫和猫都投与IMammal。
汤姆·德洛福德

1
您可以将哺乳动物试铸成狗。如果是狗,那就是狗。否则,它变为空。如果cat具有这些允许的重载功能,则“重载”功能可使从cat到dog的不可能转换成为可能。但是,处理数据丢失并调整不存在的数据是您的工作。像将爪子变成钉子,把弦追成球追赶,等等……
TamusJRoyce 2012年

我认为这些示例有些极端和挑剔,也许强制转换是复制构造函数的捷径。例如,正在使用基础对象car中定义的属性构建法拉利。或者,从人类开始,创造一个男孩。铸造并直接使用?同意那是不可以的。但是,如果它是构造函数之类的东西,则可以工作。下面的序列化答案很不错。
sirthomas


59

您的基类引用所引用的实例不是您的子类的实例。没错

进一步来说:

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

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

为使转换成功,要向下转换的实例必须是要向下转换的类的实例(或至少要向下转换到的类必须在实例的类层次结构内),否则演员将失败。


或可能基于base otherDerived = new OtherDerived(); 派生其他失败=(派生)其他派生;
布莱尔·康拉德

class Base {} class Derived:Base {} //在主方法中,Base namedInstance = new Derived(); Base baseInstance =新的Base(); 衍生品=(衍生品)derivedInstance; 派生失败=(派生)baseInstance; 它将在.NET 3.5中正确编译。您在说什么问题?
pradeeptp,2010年

8
@pradeeptp:当然可以。谁说过编译错误?
格雷格D 2010年

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

结论:读完所有文章之后,希望它现在像有意义的父级到子级转换(案例3)一样有意义。

回答问题:

您的答案是在情况2中。在这里您可以看到OOP不允许这种强制转换,并且您试图违反OOP的基本规则之一。因此,请始终选择安全的路径。

此外,为避免此类特殊情况,.net建议使用is / as运算符,它们将帮助您做出明智的决定并提供安全的转换。


17

在某些情况下,这样的转换很有意义。
以我为例,我正在通过网络接收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,以便可以在类似的上下文中使用它?还是对您的需求不重要?
tobriand 2015年

有时包括可以适当地替代继承
Vahid Ghadiri 18'Apr

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),就像使用大铁锤破解鸡蛋一样。为什么不只是新建派生类型并自己分配它的属性(这是1行代码)。
bytedev

9

保罗,您没有问'我能做'-我假设您想知道如何做!

我们必须在一个项目上执行此操作-我们只用一次通用方法设置了许多类,然后初始化了派生类的特定属性。我使用VB,所以我的示例在VB中(艰难的实践),但是我从该站点偷了VB示例,该站点也具有更好的C#版本:

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控件类型的变量中。当然,“控件”可以使对象在“确定”附近移动,并且您可以处理与文本框有关的所有“控件”问题(例如,top,left,.text属性)。如果不将指向文本框的“ control”类型变量转换为指向内存中的文本框,则无法看到特定于文本框的内容(例如,.multiline),但仍在内存中。

现在想象一下,您有一个控件,并且想要将文本框类型的变量设置为大小写。内存中的控件缺少“多行”和其他文本框内容。如果您尝试引用它们,则该控件不会神奇地增加多行属性!属性(看看它像一个成员变量在这里,实际存储的值-因为在文本框实例的内存)必须存在。记住,既然您正在投射,它必须是您指向的对象。因此,这不是语言上的限制,从哲学上讲,以这种方式进行案例化是不可能的。


1
我知道这是事实,但您应该在“ If dstProperty Is Not Nothing”测试中包括“ AndAlso dstProperty.CanWrite”,以确保其不是只读属性。
JamesMLV

@JamesMLV-谢谢大家。“事后” -看起来并不像OP打算无论如何要接受任何答案:-(所以没有事实是后哦。
FastAl

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

要进行投射,实际对象的类型必须等于或衍生 您尝试投射为的类型。

或者,以相反的方式声明它,您尝试将其强制转换为的Type必须与对象的实际类型相同或作为其基本类。

如果您的实际对象是Baseclass类型,则不能将其强制转换为派生类Type ...


1

针对使用ServiceStack的人员的序列化方法的一种变化:

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

或更详细:

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

ServiceStack的序列化可能非常快,而且很明显,但是显然,这不是针对低延迟传输中的大量转换或高度复杂类型的解决方案。对于使用ServiceStack的任何人来说,这可能都是显而易见的,但是我想我会在评论之前进行澄清。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.