Incapaz de transmitir da classe dos pais para a classe da criança


97

Estou tentando converter de uma classe pai para uma classe filha, mas recebo uma InvalidCastException. A classe filha possui apenas uma propriedade do tipo int. Alguém sabe o que preciso fazer?


Também é bom saber que você não pode usar conversão explícita para classes básicas / derivadas relacionadas.
Rzassar

Respostas:


133

Uma maneira simples de fazer downcast em C # é serializar o pai e depois desserializá-lo no filho.

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

Eu tenho um aplicativo de console simples que converte animal em cachorro, usando as duas linhas de código acima aqui


19
Bem, eu hesitaria em chamar isso de "abatido".
Kirk Woll

Apenas uma observação: os nomes das variáveis ​​não são os mesmos acima.
Jake Gaston

5
Adoro quando alguém pensa fora da caixa e silencia as pessoas dizendo ao OP que isso não pode ser feito (exceto por um ou dois trolls)! Obrigado pela ajuda neste. Tenho tentado descobrir isso nas últimas horas :)
derekmx271

3
Esta é uma excelente solução. Tive um caso em que minha classe filha era apenas um invólucro para um pai sem nenhuma funcionalidade adicional. Fiz isso para não precisar importar a referência da web para meu aplicativo, pois ela estava em minha biblioteca auxiliar. Isso me permitiu converter o pai em minha classe de wrapper. Obrigado!
BrianVPS de

1
Você é um gênio! :)
Yablargo

118

Você não pode lançar um mamífero em um cachorro - pode ser um gato.

Você não pode jogar comida em um sanduíche - pode ser um cheeseburger.

Você não pode lançar um carro em uma Ferrari - pode ser uma Honda, ou mais especificamente, você não pode lançar um Ferrari 360 Modena em uma Ferrari 360 Challange Stradale - há peças diferentes, embora sejam ambos Ferrari 360.


17
Bloqueios de estrada compreensíveis, daí a impossibilidade de realmente 'lançar' dessa maneira. Mas e se ele quiser um cão com a mesma cor de olhos / peso / padrão de cabelo / idade, etc. que o gato que está sendo segurado no objeto mamífero? Copiar essencialmente as propriedades comuns.
FastAl

7
FastAl, é exatamente por isso que temos interfaces. Mammal deve implementar IMammal e conter a cor dos olhos, peso, etc. Agora você pode lançar cão e gato para IMammal.
Tom Deloford

1
Você pode transformar mamífero em cachorro. Se for um cachorro, é um cachorro. Caso contrário, torna-se nulo. As funções de "sobrecarga" podem tornar a conversão impossível de gato em cachorro possível, se o gato tem essas funções sobrecarregadas que permitem isso. Mas é seu trabalho lidar com a perda de dados e adaptar os dados não existentes. Como converter garras em pregos, perseguir barbante em perseguir bola, etc ...
TamusJRoyce

Acho que os exemplos são um pouco extremos e seletivos e talvez o elenco seja um atalho para um construtor de cópia. Por exemplo, construir um ferrari com as propriedades definidas no objeto base carro. Ou comece com um humano e crie um Menino. Fundição e uso direto? Concordo que é um não-não. Mas se for parte do construtor ou algo assim, pode funcionar. A resposta de serialização abaixo é um bom toque.
sirthomas

1
Analogia de Ferrari NICE
Lord Darth Vader

57

A instância à qual sua referência de classe base está se referindo não é uma instância de sua classe filha. Não há nada errado.

Mais especificamente:

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

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

Para que o elenco seja bem-sucedido, a instância para a qual você está fazendo o downcast deve ser uma instância da classe para a qual você está fazendo o downcast (ou, pelo menos, a classe para a qual você está fazendo o downcast deve estar dentro da hierarquia de classes da instância), caso contrário, elenco irá falhar.


ou potencialmente Base otherDerived = new OtherDerived (); Derivado otherFail = (Derivado) otherDerived;
Blair Conrad

class Base {} class Derived: Base {} // No método principal Base associatedInstance = new Derived (); BaseInstance base = nova Base (); Bem derivado = instância derivada (derivada); Falha derivada = (Derivado) baseInstance; Compila sem nenhum erro no .NET 3.5. Onde está o problema que você está dizendo?
pradeeptp

7
@pradeeptp: Claro que constrói. Quem falou em erro de compilação?
Greg D de

17

Existem alguns casos em que tal elenco faria sentido.
No meu caso, estava recebendo uma aula de BASE pela rede e precisava de mais recursos. Portanto, derivar para lidar com isso do meu lado com todos os sinos e assobios que eu queria, e lançar a classe BASE recebida na DERIVED simplesmente não era uma opção (Throws InvalidCastException of Course)

Uma SOLUÇÃO prática pensada para fora da caixa era declarar uma classe EXTENSION Helper que NÃO estava herdando a classe BASE na verdade, mas a INCLUINDO como um membro.

public class BaseExtension
{
   Base baseInstance;

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

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

Se você tem um acoplamento fraco e precisa apenas de alguns recursos extras para a classe base, sem REALMENTE ter uma necessidade absoluta de derivação, essa pode ser uma solução rápida e simples.


Estou certo em pensar que você provavelmente deseja que você BaseExtensionaqui, pelo menos, implemente de IBaseforma que possa usá-lo em contextos semelhantes? Ou não era importante para as suas necessidades?
tobriand,

algumas vezes, incluindo pode ser uma substituição apropriada para herança
Vahid Ghadiri

17

Tenho visto muitas pessoas dizendo que o elenco explícito de pais para filhos não é possível, isso não é verdade. Vamos dar um início revisado e tentar prová-lo com exemplos.

Como sabemos em .net, todos os fundidos têm duas categorias amplas.

  1. Para tipo de valor
  2. Para o tipo de referência (no seu caso, o tipo de referência)

O tipo de referência tem mais três casos situacionais principais em que qualquer cenário pode estar.

Criança para pai (elenco implícito - sempre bem-sucedido)

Caso 1. Filho de qualquer pai direto ou indireto

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

Pai para filho (elenco explícito - pode ser bem-sucedido)

Caso 2. Variável pai segurando o objeto pai (não permitido)

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

Caso 3. Variável pai segurando objeto filho (sempre bem-sucedido)

Nota: Como os objetos têm natureza polimórfica, é possível que uma variável de um tipo de classe pai mantenha um tipo filho.

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

Conclusão: Depois de ler acima de tudo, espero que faça sentido agora, por exemplo, como a conversão de pais para filhos é possível (Caso 3).

Responda a pergunta :

Sua resposta está no caso 2. Onde você pode ver que tal transmissão não é permitida pela OOP e você está tentando violar uma das regras básicas da OOP. Portanto, sempre escolha o caminho seguro.

Além disso, para evitar tais situações excepcionais .net recomendou usar is / as operadores que irão ajudá-lo a tomar decisões informadas e fornecer fundição segura.


13

Isso violaria os princípios orientados a objetos. Eu diria que uma solução elegante aqui e em outras partes do projeto é usar uma estrutura de mapeamento de objetos como o AutoMapper para configurar uma projeção.

Aqui está uma configuração um pouco mais complexa do que o necessário, mas é flexível o suficiente para a maioria dos casos:

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>();
        });
    }
}

Quando o aplicativo inicia a chamada AutoMapperConfiguration.Configure()e você pode projetar assim:

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

As propriedades são mapeadas por convenção, portanto, se a classe for herdada, os nomes das propriedades serão exatamente os mesmos e o mapeamento será configurado automaticamente. Você pode adicionar propriedades adicionais ajustando a configuração. Veja a documentação .


Usar o Automapper para mapear um tipo com uma única propriedade para outro (como o OP descrito) é como usar uma marreta para quebrar um ovo. Por que não apenas criar o tipo derivado e atribuir você mesmo sua propriedade (que é 1 linha de código).
bytedev

9

Paul, você não perguntou 'Posso fazer isso' - presumo que você queira saber como fazer!

Tivemos que fazer isso em um projeto - há muitas classes que configuramos de maneira genérica apenas uma vez e, em seguida, inicializamos propriedades específicas para classes derivadas. Eu uso VB, então minha amostra está em VB (noogies difíceis), mas eu roubei a amostra VB deste site que também tem uma versão melhor em C #:

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

Código de amostra:

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

Claro que isso não é realmente um elenco. Ele está criando um novo objeto derivado e copiando as propriedades do pai, deixando as propriedades do filho em branco. Isso é tudo que eu precisava fazer e parece que é tudo o que você precisa fazer. Observe que ele apenas copia propriedades, não membros (variáveis ​​públicas) da classe (mas você pode estendê-lo para fazer isso se tiver vergonha de expor membros públicos).

Casting em geral cria 2 variáveis ​​apontando para o mesmo objeto (mini tutorial aqui, por favor, não jogue exceções de canto para mim). Existem ramificações significativas para isso (exercício para o leitor)!

É claro que devo dizer por que a língua não permite que você vá da base para a instância derivada, mas o faz de outra maneira. imagine um caso onde você pode pegar uma instância de uma caixa de texto winforms (derivada) e armazená-la em uma variável do tipo controle Winforms. Claro que o 'controle' pode mover o objeto OK e você pode lidar com todas as coisas de 'controle' sobre a caixa de texto (por exemplo, topo, esquerda, propriedades do texto). As coisas específicas da caixa de texto (por exemplo, .multiline) não podem ser vistas sem lançar a variável do tipo 'controle' apontando para a caixa de texto na memória, mas ainda está lá na memória.

Agora imagine que você tem um controle e deseja case uma variável do tipo caixa de texto para ele. O controle na memória está faltando 'multilinha' e outras coisas de caixa de texto. Se você tentar referenciá-los, o controle não aumentará magicamente uma propriedade de várias linhas! A propriedade (olhe para ela como uma variável de membro aqui, que realmente armazena um valor - porque está na memória da instância da caixa de texto) deve existir. Já que você está lançando, lembre-se de que deve ser o mesmo objeto para o qual você está apontando. Conseqüentemente, não é uma restrição de idioma, é filosoficamente impossível casar dessa maneira.


1
Sei que isso ocorre depois do fato, mas você deve incluir "AndAlso dstProperty.CanWrite" em seu teste "If dstProperty IsNot Nothing", para garantir que não seja uma propriedade somente leitura.
JamesMLV

@JamesMLV - obrigado, boa pegada. 'após o fato' - não parece que o OP aceitará qualquer resposta de qualquer maneira :-( portanto, não há fato a ser perseguido.
Muito

4

A instância do objeto deve ser criada usando o tipo da classe filha, você não pode lançar uma instância do tipo pai para um tipo filho


2

Para mim, foi o suficiente copiar todos os campos de propriedade da classe base para o pai assim:

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;
}

Uma solução universal para qualquer objeto pode ser encontrada aqui


2

A partir do C # 7.0, você pode usar a palavra - chave is para fazer isso:

Com essas classes definidas:

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

Você pode então fazer algo como:

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

O que seria equivalente a:

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

Agora você pode ligar para:

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

Assim como

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

Dessa forma, você pode ter certeza de que seu downcast será válido e não lançará uma exceção!


1

Para lançar, o objeto real deve ser de um Tipo igual ou derivado do Tipo para o qual você está tentando lançar ...

ou, para afirmar de maneira oposta, o tipo para o qual você está tentando convertê-lo deve ser o mesmo ou uma classe base do tipo real do objeto.

se o seu objeto real for do tipo Baseclass , você não pode lançá-lo em uma classe derivada Type ...


1

Uma variação da abordagem de serialização para quem usa o ServiceStack:

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

ou o mais detalhado:

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

A serialização do ServiceStack pode ser super rápida e tudo, mas claramente, esta não é uma solução para conversões massivas em transferências de baixa latência, nem para tipos altamente complexos. Isso é provavelmente óbvio para qualquer pessoa que use o ServiceStack, mas pensei em esclarecer antes dos comentários.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.