É possível atribuir um objeto de classe base para uma referência de classe derivada com um typecast explícito?

StackOverflow https://stackoverflow.com/questions/729527

Pergunta

É possível atribuir um objeto de classe base para uma referência de classe derivada com um typecast explícito em C # ?.

Eu tentei isso e ele cria um erro de tempo de execução.

Foi útil?

Solução

No. Uma referência a uma classe de derivados deve, na verdade, referem-se a uma instância da classe derivada (ou nulo). Caso contrário, como é que você espera que ele se comportar?

Por exemplo:

object o = new object();
string s = (string) o;
int i = s.Length; // What can this sensibly do?

Se você quer ser capaz de converter uma instância do tipo base para o tipo derivado, eu sugiro que você escrever um método para criar uma instância tipo apropriado derivado. Ou olhar para a sua árvore de herança de novo e tentar redesenhar de modo que você não precisa fazer isso em primeiro lugar.

Outras dicas

Não, isso não é possível desde que atribuí-la a uma referência de classe derivada seria como dizer "Classe base é um substituto plenamente capaz de classe derivada, que pode fazer tudo o que a classe derivada pode fazer", o que não é verdade, pois as classes derivadas na oferta geral mais funcionalidades do que sua classe base (pelo menos, essa é a idéia por trás de herança).

Você poderia escrever um construtor na classe derivada de tomar um objeto de classe base como parâmetro, copiando os valores.

Algo parecido com isto:

public class Base {
    public int Data;

    public void DoStuff() {
        // Do stuff with data
    }
}

public class Derived : Base {
    public int OtherData;

    public Derived(Base b) {
        this.Data = b.Data;
        OtherData = 0; // default value
    }

    public void DoOtherStuff() {
        // Do some other stuff
    }
}

Nesse caso, você deve copiar o objeto base e obter um objeto de classe derivada totalmente funcional com valores padrão para os membros derivados. Desta forma, você também pode evitar o problema apontado por Jon Skeet:

Base b = new Base();
Dervided d = new Derived();

b.DoStuff();    // OK
d.DoStuff();    // Also OK
b.DoOtherStuff();    // Won't work!
d.DoOtherStuff();    // OK

d = new Derived(b);  // Copy construct a Derived with values of b
d.DoOtherStuff();    // Now works!

Eu tive esse problema e resolveu-lo adicionando um método que leva um parâmetro de tipo e converte o objeto atual para esse tipo.

public TA As<TA>() where TA : Base
{
    var type = typeof (TA);
    var instance = Activator.CreateInstance(type);

     PropertyInfo[] properties = type.GetProperties();
     foreach (var property in properties)
     {
         property.SetValue(instance, property.GetValue(this, null), null);
     }

     return (TA)instance;
}

Isso significa que você pode usá-lo em você código como este:

var base = new Base();
base.Data = 1;
var derived = base.As<Derived>();
Console.Write(derived.Data); // Would output 1

Como muitos outros já respondeu: Não.

Eu uso o seguinte código nessas ocasiões infelizes quando eu preciso usar um tipo de base como um tipo derivado. Sim, é uma violação da substituição de Liskov (LSP) e sim na maioria das vezes nós somos a favor composição sobre herança. Props para Markus Knappen Johansson cuja resposta original deste baseia-se.

Este código na classe base:

    public T As<T>()
    {
        var type = typeof(T);
        var instance = Activator.CreateInstance(type);

        if (type.BaseType != null)
        {
            var properties = type.BaseType.GetProperties();
            foreach (var property in properties)
                if (property.CanWrite)
                    property.SetValue(instance, property.GetValue(this, null), null);
        }

        return (T) instance;
    }

Permite:

    derivedObject = baseObect.As<derivedType>()

Uma vez que usa a reflexão, é "caro". Use conformidade.

Não, não é possível, portanto, o seu erro de execução.

Mas você pode atribuir uma instância de uma classe derivada para uma variável do tipo classe base.

Como todos aqui disse, isso não é possível diretamente.

O método que eu prefiro e é bastante limpo, é usar um objeto Mapper como AutoMapper .

Ele vai fazer a tarefa de propriedades cópia de uma instância para outra (não necessariamente do mesmo tipo) automaticamente.

Você pode lançar uma variável que é digitado como a base de classe para o tipo de uma classe derivada; no entanto, por necessidade isso vai fazer uma verificação de tempo de execução, para ver se o objeto real envolvido é do tipo correto.

Uma vez criado, o tipo de um objeto não pode ser alterado (não menos importante, ele pode não ter o mesmo tamanho). Você pode, no entanto, converso uma instância, criando um new exemplo do segundo tipo - mas você precisa escrever o código de conversão manualmente

.

Expandindo resposta de @ YBO - não é possível porque a instância que você tem da classe base não é realmente uma instância da classe derivada. Ele só sabe sobre os membros da classe base, e não sabe nada sobre os da classe derivada.

A razão que você pode lançar uma instância da classe derivada para uma instância da classe base é porque a classe derivada de fato já é uma instância da classe base, uma vez que tem os membros já. O oposto não pode ser dito.

Não, não é possível.

Considere um cenário em que um ACBus é uma classe derivada da classe base Bus. ACBus tem características como TurnOnAC e TurnOffAC que operam em um campo chamado ACState. TurnOnAC define ACState para ligado e TurnOffAC define ACState para fora. Se você tentar usar TurnOnAC e TurnOffAC recursos do autocarro, não faz sentido.

class Program
{
    static void Main(string[] args)
    {
        a a1 = new b();  
        a1.print();  
    }
}
class a
{
    public a()
    {
        Console.WriteLine("base class object initiated");
    }
    public void print()
    {
        Console.WriteLine("base");
    }
}
class b:a
{
    public b()
    {
        Console.WriteLine("child class object");
    }
    public void print1()
    {
        Console.WriteLine("derived");
    }
}

}

quando criamos um objeto de classe criança, o objeto da classe base é auto iniciado variável de referência classe tão vil pode apontar para objeto de classe criança.

mas não vice-versa, pois uma variável de referência classe criança não pode apontar para objecto de classe de base porque nenhum objecto classe criança é criada.

e também observe que a variável de referência classe base só pode chamar membro da classe base.

Há realmente uma maneira de fazer isso. Pense em como você pode usar Newtonsoft JSON para desserializar um objeto JSON. Ele vai (ou pelo menos lata) ignorar elementos em falta e preencher todos os elementos que ele não conhece.

Então aqui está como eu fiz isso. Um exemplo de código pequeno vai seguir a minha explicação.

  1. Criar uma instância de seu objeto da classe base e preenchê-lo em conformidade.

  2. Usando a classe "jsonconvert" de Newtonsoft json, serialize o objeto em uma string JSON.

  3. Agora crie o seu sub objeto de classe por deserializing com a string json criou na etapa 2. Isto irá criar uma instância de sua sub classe com todas as propriedades da classe base.

Isso funciona como um encanto! Então .. quando isso é útil? Algumas pessoas perguntaram quando isso faria sentido e sugeriu mudar o esquema do OP para acomodar o fato de que você não pode nativamente fazer isso com a herança de classe (em .net).

No meu caso, eu tenho uma classe de configurações que contém todas as configurações de "base" para um serviço. serviços específicos têm mais opções e aqueles provenientes de uma tabela DB diferente, então essas classes herdam da classe base. Todos eles têm um conjunto diferente de opções. Então, quando recuperar os dados para um serviço, é muito mais fácil a FIRST preencher os valores usando uma instância do objeto base. Um método para fazer isso com uma única consulta DB. Logo após isso, criar o objecto sub classe utilizando o método descrito acima. Eu, então, fazer uma segunda consulta e preencher todos os valores dinâmicos no objeto sub classe.

O resultado final é uma classe derivada com todas as opções definidas. Repetindo isso por novos sub classes adicionais leva apenas algumas linhas de código. É simples, e ele usa um pacote muito experimentado e testado (Newtonsoft) para fazer o trabalho de magia.

Este código de exemplo é vb.Net, mas você pode facilmente converter para c #.

' First, create the base settings object.
    Dim basePMSettngs As gtmaPayMethodSettings = gtmaPayments.getBasePayMethodSetting(payTypeId, account_id)
    Dim basePMSettingsJson As String = JsonConvert.SerializeObject(basePMSettngs, Formatting.Indented)

    ' Create a pmSettings object of this specific type of payment and inherit from the base class object
    Dim pmSettings As gtmaPayMethodAimACHSettings = JsonConvert.DeserializeObject(Of gtmaPayMethodAimACHSettings)(basePMSettingsJson)

Pode não ser relevent, mas eu era capaz de executar código em um objeto derivado dada a sua base. É definitivamente mais hacky do que eu gostaria, mas funciona:

public static T Cast<T>(object obj)
{
    return (T)obj;
}

...

//Invoke parent object's json function
MethodInfo castMethod = this.GetType().GetMethod("Cast").MakeGenericMethod(baseObj.GetType());
object castedObject = castMethod.Invoke(null, new object[] { baseObj });
MethodInfo jsonMethod = baseObj.GetType ().GetMethod ("ToJSON");
return (string)jsonMethod.Invoke (castedObject,null);

Você pode usar uma extensão:

public static void CopyOnlyEqualProperties<T>(this T objDest, object objSource) where T : class
    {
        foreach (PropertyInfo propInfo in typeof(T).GetProperties())
            if (objSource.GetType().GetProperties().Any(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType()))
                propInfo.SetValue(objDest, objSource.GetType().GetProperties().First(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType()).GetValue(objSource));
    }

Em cupom:

public class BaseClass
{
  public string test{ get; set;}
}
public Derived : BaseClass
{
//Some properies
}

public void CopyProps()
{
   BaseClass baseCl =new BaseClass();
   baseCl.test="Hello";
   Derived drv=new Derived();
   drv.CopyOnlyEqualProperties(baseCl);
   //Should return Hello to the console now in derived class.
   Console.WriteLine(drv.test);

}

Eu sei que este é antiga, mas eu usei isso com sucesso por um bom tempo.

   private void PopulateDerivedFromBase<TB,TD>(TB baseclass,TD derivedclass)
    {
        //get our baseclass properties
        var bprops = baseclass.GetType().GetProperties();
        foreach (var bprop in bprops)
        {
            //get the corresponding property in the derived class
            var dprop = derivedclass.GetType().GetProperty(bprop.Name);
            //if the derived property exists and it's writable, set the value
            if (dprop != null && dprop.CanWrite)
                dprop.SetValue(derivedclass,bprop.GetValue(baseclass, null),null);
        }
    } 

Solução com JsonConvert (em vez de typecast)

Hoje eu enfrentei o mesmo problema e eu encontrei uma solução simples e rápida para o problema usando JsonConvert.

var base = new BaseClass();
var json = JsonConvert.SerializeObject(base);
DerivedClass derived = JsonConvert.DeserializeObject<DerivedClass>(json);

Outra solução é adicionar o método de extensão como assim:

 public static void CopyProperties(this object destinationObject, object sourceObject, bool overwriteAll = true)
        {
            try
            {
                if (sourceObject != null)
                {
                    PropertyInfo[] sourceProps = sourceObject.GetType().GetProperties();
                    List<string> sourcePropNames = sourceProps.Select(p => p.Name).ToList();
                    foreach (PropertyInfo pi in destinationObject.GetType().GetProperties())
                    {
                        if (sourcePropNames.Contains(pi.Name))
                        {
                            PropertyInfo sourceProp = sourceProps.First(srcProp => srcProp.Name == pi.Name);
                            if (sourceProp.PropertyType == pi.PropertyType)
                                if (overwriteAll || pi.GetValue(destinationObject, null) == null)
                                {
                                    pi.SetValue(destinationObject, sourceProp.GetValue(sourceObject, null), null);
                                }
                        }
                    }
                }
            }
            catch (ApplicationException ex)
            {
                throw;
            }
        }

, em seguida, ter um construtor em cada classe derivada que aceita classe base:

  public class DerivedClass: BaseClass
    { 
        public DerivedClass(BaseClass baseModel)
        {
            this.CopyProperties(baseModel);
        }
    }

Será propriedades de destino também opcionalmente sobrescrever se já definido (não nulo) ou não.

É possível atribuir um objeto de classe base para uma referência de classe derivada com um typecast explícito em C # ?.

Não só explícito, mas também conversões implícitas são possíveis.

C # linguagem não permite que tais operadores de conversão, mas você ainda pode escrevê-los usando puro C # e eles trabalham. Note-se que a classe que define o operador implícito conversão (Derived) e a classe que utiliza o operador (Program) deve ser definido em conjuntos separados (por exemplo, a classe Derived é em um library.dll que é referenciado por program.exe contendo a classe Program).

//In library.dll:
public class Base { }

public class Derived {
    [System.Runtime.CompilerServices.SpecialName]
    public static Derived op_Implicit(Base a) {
        return new Derived(a); //Write some Base -> Derived conversion code here
    }

    [System.Runtime.CompilerServices.SpecialName]
    public static Derived op_Explicit(Base a) {
        return new Derived(a); //Write some Base -> Derived conversion code here
    }
}

//In program.exe:
class Program {
    static void Main(string[] args) {
        Derived z = new Base(); //Visual Studio can show squiggles here, but it compiles just fine.
    }
}

Quando você faz referência a biblioteca utilizando a referência de projeto no Visual Studio, VS mostra rabiscos quando você usa a conversão implícita, mas ele compila apenas multa. Se você acabou de fazer referência ao library.dll, não há rabiscos.

Você pode fazer isso usando o genérico.

public class BaseClass
{
    public int A { get; set; }
    public int B { get; set; }
    private T ConvertTo<T>() where T : BaseClass, new()
    {
         return new T
         {
             A = A,
             B = B
         }
    }

    public DerivedClass1 ConvertToDerivedClass1()
    {
         return ConvertTo<DerivedClass1>();
    }

    public DerivedClass2 ConvertToDerivedClass2()
    {
         return ConvertTo<DerivedClass2>();
    }
}

public class DerivedClass1 : BaseClass
{
    public int C { get; set; }
}

public class DerivedClass2 : BaseClass
{
    public int D { get; set; }
}

Você tem três benefícios usando esta abordagem.

  1. Você não está duplicando o código
  2. Você não está usando a reflexão (que é lento)
  3. Todas as suas conversões estão em um lugar

Eu combinei algumas partes das respostas anteriores (graças a esses autores) e montar uma classe estática simples com dois métodos que estamos usando.

Sim, é simples, não, não cobre todos os cenários, sim, poderia ser expandida e fez melhor, não, não é perfeito, sim, poderia ser mais eficiente, não, não é a melhor coisa desde o pão cortado, sim, existem full-on mapeadores pacote NuGet objeto robustos lá fora, que são muito melhor para uso pesado, etc etc, yada yada - mas funciona para as nossas necessidades básicas embora :)

E é claro que vai tentar mapear valores de qualquer objeto a qualquer objeto, derivado ou não. (Apenas as propriedades públicas que são nomeados a mesma é claro - ignora o resto)

USO:

SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 };

// creates new object of type "RealPerson" and assigns any matching property 
// values from the puppet object 
// (this method requires that "RealPerson" have a parameterless constructor )
RealPerson person = ObjectMapper.MapToNewObject<RealPerson>(puppet);

// OR

// create the person object on our own 
// (so RealPerson can have any constructor type that it wants)
SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 };
RealPerson person = new RealPerson("tall") {Name = "Steve"};

// maps and overwrites any matching property values from 
// the puppet object to the person object so now our person's age will get set to 5 and
// the name "Steve" will get overwritten with "Elmo" in this example
ObjectMapper.MapToExistingObject(puppet, person);

STATIC UTILIDADE CLASSE:

public static class ObjectMapper
{
    // the target object is created on the fly and the target type 
    // must have a parameterless constructor (either compiler-generated or explicit) 
    public static Ttarget MapToNewObject<Ttarget>(object sourceobject) where Ttarget : new()
    {
        // create an instance of the target class
        Ttarget targetobject = (Ttarget)Activator.CreateInstance(typeof(Ttarget));

        // map the source properties to the target object
        MapToExistingObject(sourceobject, targetobject);

        return targetobject;
    }

    // the target object is created beforehand and passed in
    public static void MapToExistingObject(object sourceobject, object targetobject)
    {
        // get the list of properties available in source class
        var sourceproperties = sourceobject.GetType().GetProperties().ToList();

        // loop through source object properties
        sourceproperties.ForEach(sourceproperty => {

            var targetProp = targetobject.GetType().GetProperty(sourceproperty.Name);

            // check whether that property is present in target class and is writeable
            if (targetProp != null && targetProp.CanWrite)
            {
                // if present get the value and map it
                var value = sourceobject.GetType().GetProperty(sourceproperty.Name).GetValue(sourceobject, null);
                targetobject.GetType().GetProperty(sourceproperty.Name).SetValue(targetobject, value, null);
            }
        });
    }
}

Como sobre: ??

public static T As<T>(this object obj)
    {
        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj));
    }

A melhor maneira de adicionar todas as propriedades de base do item derivado é o uso de reflexão em costructor. Tente este código, sem criar métodos ou instâncias.

    public Derived(Base item) :base()
    {

        Type type = item.GetType();

        System.Reflection.PropertyInfo[] properties = type.GetProperties();
        foreach (var property in properties)
        {
            try
            {
                property.SetValue(this, property.GetValue(item, null), null);
            }
            catch (Exception) { }
        }

    }

Eu discordo que não é possível. Você pode fazê-lo como este:

public class Auto 
{ 
    public string Make {get; set;}
    public string Model {get; set;}
}

public class Sedan : Auto
{ 
    public int NumberOfDoors {get; set;}
}

public static T ConvertAuto<T>(Sedan sedan) where T : class
{
    object auto = sedan;
    return (T)loc;
}

Uso:

var sedan = new Sedan();
sedan.NumberOfDoors = 4;
var auto = ConvertAuto<Auto>(sedan);

Não, veja esta questão que eu pedi - Upcasting em .NET usando os genéricos

A melhor maneira é fazer com que um construtor padrão da classe, construção e, em seguida, chamar um método Initialise

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top