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?
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?
Respostas:
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
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.
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.
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.
BaseExtensionaqui, pelo menos, implemente de IBaseforma que possa usá-lo em contextos semelhantes? Ou não era importante para as suas necessidades?
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.
O tipo de referência tem mais três casos situacionais principais em que qualquer cenário pode estar.
Caso 1. Filho de qualquer pai direto ou indireto
Employee e = new Employee();
Person p = (Person)e; //Allowed
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.
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 .
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 #:
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.
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
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!
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 ...
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.