E 'possibile assegnare un oggetto classe base ad un riferimento di classe derivata con un typecast esplicito?

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

Domanda

E 'possibile assegnare un oggetto classe base ad un riferimento di classe derivata con un typecast esplicito in C # ?.

Ho provato e si crea un errore di run-time.

È stato utile?

Soluzione

No. Un riferimento a una classe derivata deve effettivamente riferimento a un'istanza della classe derivata (o nullo). Altrimenti come ci si aspetterebbe che comportarsi?

Ad esempio:

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

Se si vuole essere in grado di convertire un'istanza del tipo di base al tipo derivato, vi consiglio di scrivere un metodo per creare un appropriato derivato esempio tipo. Oppure guardate il vostro albero di ereditarietà di nuovo e tenta di ridisegnare in modo che non c'è bisogno di fare questo in primo luogo.

Altri suggerimenti

No, questo non è possibile in quanto l'assegnazione ad un riferimento classe derivata sarebbe come dire "Classe di base è un sostituto pienamente in grado di classe derivata, si può fare tutto ciò che la classe derivata può fare", che non è vero in quanto le classi derivate in generale, offrire più funzionalità rispetto la loro classe di base (almeno, questa è l'idea alla base di eredità).

Si potrebbe scrivere un costruttore della classe derivata di prendere un oggetto di classe di base come parametro, copiare i valori.

Qualcosa di simile a questo:

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

In questo caso si dovrà copiare l'oggetto di base e ottenere un oggetto di classe derivata completamente funzionale con i valori di default per i membri derivati. In questo modo è anche possibile evitare il problema segnalato da 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!

Ho avuto questo problema e risolto aggiungendo un metodo che accetta un parametro di tipo e converte l'oggetto corrente in quel 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;
}

Ciò significa che è possibile utilizzarlo in voi codice come questo:

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

Come molti altri hanno risposto, No.

Io uso il seguente codice in quelle occasioni sfortunati quando ho bisogno di utilizzare un tipo di base come un tipo derivato. Sì, è una violazione del principio di sostituzione di Liskov (LSP) e sì la maggior parte del tempo si favorisce la composizione per l'eredità. Puntelli per Markus Johansson Knappen la cui risposta originale questo si basa su.

Questo codice nella 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;
    }

Permette:

    derivedObject = baseObect.As<derivedType>()

Dal momento che utilizza la riflessione, è "costoso". Utilizzare di conseguenza.

No, non è possibile, quindi, il vostro errore di runtime.

Ma è possibile assegnare un'istanza di una classe derivata a una variabile di tipo classe di base.

Come tutti qui detto, questo non è possibile direttamente.

Il metodo che preferisco ed è piuttosto pulito, è quello di utilizzare un oggetto come Mapper automapper .

Si farà il compito di proprietà di copiatura da un'istanza all'altra (non necessariamente dello stesso tipo) in modo automatico.

È possibile lanciare un variabile che viene digitato come classe base al tipo di una classe derivata; tuttavia, per necessità questo farà un controllo runtime, per vedere se l'oggetto effettivo in questione è del tipo corretto.

Una volta creato, il tipo di un oggetto non può essere cambiato (non ultimo, potrebbe non essere la stessa dimensione). È possibile, tuttavia, convertire un esempio, la creazione di un nuovo esempio del secondo tipo -. Ma è necessario scrivere il codice di conversione manualmente

Ampliando la risposta di @ YBO - non è possibile perché l'istanza si ha della classe base non è in realtà un'istanza della classe derivata. Si conosce solo i membri della classe base, e non sa nulla di quelli della classe derivata.

Il motivo per cui si può lanciare un'istanza della classe derivata a un'istanza della classe base è perché la classe derivata in realtà già è un'istanza della classe base, dal momento che ha già quei membri. Il contrario non si può dire.

No, non è possibile.

Si consideri uno scenario in cui un ACBus è una classe derivata di classe di base Bus. ACBus ha caratteristiche come TurnOnAC e TurnOffAC che operano su un campo denominato ACState. TurnOnAC imposta ACState su ON e TurnOffAC imposta ACState off. Se si tenta di utilizzare le funzionalità TurnOnAC e TurnOffAC sul bus, non ha senso.

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 creiamo un oggetto di classe del bambino, l'oggetto classe base è automatico avviato variabile di riferimento classe in modo base può puntare a oggetto di classe del bambino.

ma non viceversa perché una variabile di riferimento classe bambino non può puntare a oggetto classe base perché viene creato alcun oggetto classe bambino.

e anche notare che variabile di riferimento della classe base può chiamare solo membro della classe di base.

In realtà c'è un modo per fare questo. Pensate a come si potrebbe utilizzare Newtonsoft JSON per deserializzare un oggetto da JSON. Sarà (o almeno può) ignorare gli elementi mancanti e compilare tutti gli elementi che lo fa conoscere.

Quindi, ecco come ho fatto. Un esempio di codice piccolo seguirà la mia spiegazione.

  1. Creare un'istanza del vostro oggetto dalla classe base e popolare di conseguenza.

  2. Uso della "jsonconvert" classe di Newtonsoft JSON, serializzare quell'oggetto in una stringa JSON.

  3. Ora crea il tuo oggetto di classe sub dalla deserializzazione con la stringa JSON creato nel passaggio 2. Questo creerà un'istanza della classe sub con tutte le proprietà della classe di base.

Questo funziona come un fascino! Quindi .. quando è utile? Alcune persone hanno chiesto quando questo avrebbe senso e ha suggerito la modifica dello schema del PO per ospitare il fatto che non si può fare questo in modo nativo con classe di eredità (in Net).

Nel mio caso, ho una classe impostazioni che contiene tutte le impostazioni di "base" per un servizio. Servizi specifici hanno più opzioni e quelli provenire da una tabella DB diverso, per cui le classi ereditano la classe base. Tutti hanno un diverso insieme di opzioni. Così, quando il recupero dei dati per un servizio, è molto più facile per popolare PRIMO i valori utilizzando un'istanza dell'oggetto di base. Un metodo per fare questo con una singola query DB. Subito dopo, creo l'oggetto della classe secondaria utilizzando il metodo descritto sopra. Ho poi fare una seconda query e popolare di tutti i valori dinamici sul oggetto classe sub.

Il risultato finale è una classe derivata con tutte le opzioni impostate. Ripetendo questo per ulteriori nuove classi secondarie richiede solo poche righe di codice. E 'semplice, e utilizza un pacchetto molto collaudato (Newtonsoft) per far funzionare la magia.

Questo codice di esempio è vb.Net, ma si può facilmente convertire in 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)

potrebbe non essere relevent, ma sono stato in grado di eseguire codice su un oggetto derivato data la sua base. E 'sicuramente più hacky di quanto mi piacerebbe, ma funziona:

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

È possibile utilizzare una prolunga:

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

In Code:

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

}

So che questo è vecchio, ma ho usato questo successo per un bel po '.

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

Soluzione con JsonConvert (invece di typecast)

Oggi ho affrontato lo stesso problema e ho trovato un rapida soluzione semplice e al problema utilizzando JsonConvert.

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

Un'altra soluzione è quella di aggiungere metodo di estensione in questo modo:

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

quindi avere un costruttore in ogni classe derivata che accetta classe di base:

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

Sarà inoltre opzionalmente sovrascrivere proprietà di destinazione se già impostata (non nullo) o meno.

  

E 'possibile assegnare un oggetto classe base ad un riferimento di classe derivata con un typecast esplicito in C # ?.

Non solo esplicita, ma anche conversioni implicite sono possibili.

linguaggio C # non consente tali operatori di conversione, ma è ancora possibile scrivere utilizzando puro C # e funzionano. Si noti che la classe che definisce l'operatore implicito conversione (Derived) e la classe che utilizza l'operatore (Program) deve essere definito in assemblee separate (ad esempio la classe Derived è in una library.dll che fa riferimento program.exe contenente la 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 si fa riferimento alla libreria utilizzando il riferimento al progetto in Visual Studio, VS mostra scarabocchi quando si utilizza la conversione implicita, ma si compila bene. Se hai appena riferimento il library.dll, non ci sono scarabocchi.

È possibile farlo utilizzando generico.

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

È possibile ottenere tre vantaggi che utilizzano questo approccio.

  1. Non duplicare il codice
  2. Non si utilizza la riflessione (che è lento)
  3. Tutte le conversioni sono in un unico luogo

ho combinato alcune porzioni delle risposte precedenti (grazie a quegli autori) e mettere insieme una classe statica semplice, con due metodi che stiamo usando.

Sì, è semplice, no non copre tutti gli scenari, sì, potrebbe essere ampliato e reso migliore, no non è perfetto, sì, potrebbe essere reso più efficiente, no non è la cosa migliore dopo il pane a fette, sì, ci sono full-on robusto oggetto pacchetto NuGet mapper là fuori che sono il modo migliore per uso pesante, ecc ecc, bla bla - ma funziona per le nostre esigenze di base però :)

E naturalmente cercherà di mappare i valori da qualsiasi oggetto a qualsiasi oggetto, derivato o meno (solo le proprietà pubbliche che hanno lo stesso nome, naturalmente - ignora il 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);

STATICO UTILITY CLASS:

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

Come su:

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

Il modo migliore per aggiungere tutte le proprietà di base per oggetto derivato è l'uso riflessione in costructor. Prova questo codice, senza creare metodi o istanze.

    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) { }
        }

    }

Non sono d'accordo che non è possibile. Si può fare in questo modo:

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

Utilizzo:

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

No, vedi questa domanda che ho chiesto - upcasting in .NET utilizzando farmaci generici

Il modo migliore è quello di fare un costruttore di default sulla classe, costruire e quindi chiamare un metodo Initialise

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top