È possibile assegnare un oggetto di classe base a un riferimento a una classe derivata con un typecast esplicito in C # ?.
L'ho provato e crea un errore di runtime.
È possibile assegnare un oggetto di classe base a un riferimento a una classe derivata con un typecast esplicito in C # ?.
L'ho provato e crea un errore di runtime.
Risposte:
No. Un riferimento a una classe derivata deve effettivamente fare riferimento a un'istanza della classe derivata (o null). Altrimenti come ti aspetteresti che si comporti?
Per esempio:
object o = new object();
string s = (string) o;
int i = s.Length; // What can this sensibly do?
Se vuoi essere in grado di convertire un'istanza del tipo base nel tipo derivato, ti suggerisco di scrivere un metodo per creare un'istanza del tipo derivato appropriato. Oppure guarda di nuovo il tuo albero dell'eredità e prova a riprogettare in modo da non doverlo fare in primo luogo.
Derived
, ma puoi considerare un Derived
riferimento come un Base
riferimento.
Base
e l'altro crea un'istanza di Derived
. Se chiami un metodo virtuale su b
cui è stato eseguito l'override Derived
, vedrai il Derived
comportamento se hai un'istanza di Derived
. Ma non è proprio appropriato entrare nei dettagli in un thread di commenti di Stack Overflow: dovresti davvero leggere un buon libro o tutorial C #, poiché si tratta di cose piuttosto fondamentali.
No, non è possibile poiché assegnarlo a un riferimento a una classe derivata sarebbe come dire "La classe base è un sostituto pienamente capace della classe derivata, può fare tutto ciò che può fare la classe derivata", il che non è vero poiché le classi derivate in generale offrono più funzionalità rispetto alla loro classe base (almeno, questa è l'idea alla base dell'ereditarietà).
È possibile scrivere un costruttore nella classe derivata prendendo un oggetto della classe base come parametro, copiando i valori.
Qualcosa come 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 tal caso, si copierà l'oggetto di base e si otterrà un oggetto di classe derivata completamente funzionale con valori predefiniti per i membri derivati. In questo modo puoi anche evitare il problema segnalato da Jon Skeet:
Base b = new Base();//base class
Derived d = new Derived();//derived class
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 l'ho 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 puoi usarlo nel tuo codice in questo modo:
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.
Uso il seguente codice in quelle sfortunate occasioni in cui ho bisogno di usare un tipo base come tipo derivato. Sì, è una violazione del principio di sostituzione di Liskov (LSP) e sì, la maggior parte delle volte preferiamo la composizione all'eredità. Sostieni Markus Knappen Johansson, su cui si basa la risposta originale.
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>()
Poiché utilizza la riflessione, è "costoso". Usa di conseguenza.
user-defined conversions to or from a base class are not allowed
ne vedo le ragioni, ma sono deluso, poiché sarebbe stato molto divertente se lo avesse permesso ..
if (type.BaseType != null)
dichiarazione relativa alla A. di Markus Knappen Johansson. Perché? Ciò significa che consentirebbe un tipo nelle chiamate che non è derivato da MyBaseClass (o qualsiasi altra cosa per quella materia). Mi rendo conto che causerà ancora un errore del compilatore se Assegnato a myDerivedObject, ma se è usato solo come espressione, verrà compilato e in fase di esecuzione creerà semplicemente un myDerivedObject senza alcun dato copiato da "myBaseObject". Non riesco a immaginare un caso d'uso per questo.
Oggi ho affrontato lo stesso problema e ho trovato una soluzione semplice e rapida al problema utilizzando JsonConvert
.
var base = new BaseClass();
var json = JsonConvert.SerializeObject(base);
DerivedClass derived = JsonConvert.DeserializeObject<DerivedClass>(json);
Come tutti qui hanno detto, non è possibile direttamente.
Il metodo che preferisco ed è piuttosto pulito, è usare un Object Mapper come AutoMapper .
Eseguirà il compito di copiare automaticamente le proprietà da un'istanza all'altra (non necessariamente dello stesso tipo).
Espandendo la risposta di @ ybo: non è possibile perché l'istanza che hai della classe base non è in realtà un'istanza della classe derivata. Conosce solo i membri della classe base e non sa nulla di quelli della classe derivata.
Il motivo per cui è possibile eseguire il cast di un'istanza della classe derivata su un'istanza della classe base è perché la classe derivata in realtà è già un'istanza della classe base, poiché ha già quei membri. Non si può dire il contrario.
È possibile eseguire il cast di una variabile digitata come classe base nel tipo di una classe derivata; tuttavia, per necessità questo eseguirà un controllo in fase di esecuzione, per vedere se l'oggetto effettivo coinvolto è del tipo corretto.
Una volta creato, il tipo di oggetto non può essere modificato (non ultimo, potrebbe non essere della stessa dimensione). Tuttavia, puoi convertire un'istanza, creando una nuova istanza del secondo tipo, ma devi scrivere manualmente il codice di conversione.
No, non è possibile.
Si consideri uno scenario in cui un ACBus è una classe derivata della classe 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 su off. Se provi a utilizzare le funzionalità TurnOnAC e TurnOffAC su 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 classe figlio, l'oggetto classe base viene avviato automaticamente in modo che la variabile di riferimento della classe base possa puntare all'oggetto classe figlio.
ma non viceversa perché una variabile di riferimento di una classe figlia non può puntare a un oggetto di classe di base perché non viene creato alcun oggetto di classe figlio.
e notare anche che la variabile di riferimento della classe base può chiamare solo il membro della classe base.
In realtà c'è un modo per farlo. Pensa a come potresti utilizzare Newtonsoft JSON per deserializzare un oggetto da json. Ignorerà (o almeno può) gli elementi mancanti e popolerà tutti gli elementi di cui è a conoscenza.
Quindi ecco come l'ho fatto. Un piccolo esempio di codice seguirà la mia spiegazione.
Crea un'istanza del tuo oggetto dalla classe base e popolala di conseguenza.
Utilizzando la classe "jsonconvert" di Newtonsoft json, serializzare quell'oggetto in una stringa json.
Ora crea il tuo oggetto di sottoclasse deserializzandolo con la stringa json creata nel passaggio 2. Questo creerà un'istanza della tua sottoclasse con tutte le proprietà della classe di base.
Funziona a meraviglia! Allora .. quando è utile? Alcune persone hanno chiesto quando questo avrebbe senso e hanno suggerito di modificare lo schema dell'OP per accogliere il fatto che non è possibile farlo in modo nativo con l'ereditarietà delle classi (in .Net).
Nel mio caso, ho una classe di impostazioni che contiene tutte le impostazioni "di base" per un servizio. I servizi specifici hanno più opzioni e quelli provengono da una tabella DB diversa, quindi quelle classi ereditano la classe di base. Hanno tutti un diverso insieme di opzioni. Quindi, quando si recuperano i dati per un servizio, è molto più semplice popolare PRIMA i valori utilizzando un'istanza dell'oggetto di base. Un metodo per farlo con una singola query DB. Subito dopo, creo l'oggetto della sottoclasse usando il metodo descritto sopra. Quindi effettuo una seconda query e popolo tutti i valori dinamici sull'oggetto della sottoclasse.
L'output finale è una classe derivata con tutte le opzioni impostate. La ripetizione per ulteriori nuove sottoclassi richiede solo poche righe di codice. È semplice e utilizza un pacchetto molto collaudato (Newtonsoft) per far funzionare la magia.
Questo codice di esempio è vb.Net, ma puoi convertirlo facilmente 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)
var destObject = JsonConvert.DeserializeObject<DestinationType>(JsonConvert.SerializeObject(srcObject));
. Lo userei solo per unit test e altri "hacking" non di produzione!
Puoi usare un'estensione:
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));
}
Nel codice:
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);
}
Potrebbe non essere pertinente, ma sono stato in grado di eseguire codice su un oggetto derivato data la sua base. È decisamente più hacky di quanto vorrei, 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);
Puoi farlo usando generic.
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; }
}
Ottieni tre vantaggi utilizzando questo approccio.
So che è vecchio ma l'ho usato con 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);
}
}
Ho combinato alcune parti delle risposte precedenti (grazie a quegli autori) e ho messo insieme una semplice classe statica con due metodi che stiamo usando.
Sì, è semplice, no non copre tutti gli scenari, sì, potrebbe essere ampliato e migliorato, no non è perfetto, sì potrebbe essere reso più efficiente, no non è la cosa più grande dai tempi del pane a fette, sì ci sono mappatori di oggetti di pacchetti nuget completi e robusti là fuori che sono molto migliori per un uso intensivo, ecc. ecc.
E ovviamente proverà a mappare i valori da qualsiasi oggetto a qualsiasi oggetto, derivato o meno (solo le proprietà pubbliche che hanno lo stesso nome, ovviamente, ignorano il resto).
UTILIZZO:
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);
CLASSE DI UTILITÀ STATICA:
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);
}
});
}
}
È possibile utilizzare un costruttore di copie che richiami immediatamente il costruttore di istanze oppure, se il costruttore di istanze fa più delle assegnazioni, il costruttore di copie assegna i valori in entrata all'istanza.
class Person
{
// Copy constructor
public Person(Person previousPerson)
{
Name = previousPerson.Name;
Age = previousPerson.Age;
}
// Copy constructor calls the instance constructor.
public Person(Person previousPerson)
: this(previousPerson.Name, previousPerson.Age)
{
}
// Instance constructor.
public Person(string name, int age)
{
Name = name;
Age = age;
}
public int Age { get; set; }
public string Name { get; set; }
}
Ha fatto riferimento alla documentazione di Microsoft C # in Constructor per questo esempio che ha avuto questo problema in passato.
Un'altra soluzione è aggiungere un 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 la classe base:
public class DerivedClass: BaseClass
{
public DerivedClass(BaseClass baseModel)
{
this.CopyProperties(baseModel);
}
}
Opzionalmente sovrascriverà anche le proprietà di destinazione se già impostate (non nulle) o meno.
È possibile assegnare un oggetto di classe base a un riferimento a una classe derivata con un typecast esplicito in C # ?.
Sono possibili non solo conversioni esplicite, ma anche implicite.
Il linguaggio C # non consente tali operatori di conversione, ma puoi comunque scriverli usando il C # puro e funzionano. Si noti che la classe che definisce l'operatore di conversione implicito ( Derived
) e la classe che utilizza l'operatore ( Program
) devono essere definite in assembly separati (ad esempio, la Derived
classe è in a library.dll
cui si fa riferimento program.exe
contenendo la Program
classe).
//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 viene compilato correttamente. Se fai riferimento solo a library.dll
, non ci sono scarabocchi.
System.Runtime.CompilerServices.SpecialName
Attribute? I documenti per ogni versione dalla prima disponibile (2.0) alla "versione corrente" (4.6? "Chiunque? Chiunque?") Non dicono cosa fa, ma dicono "La classe SpecialNameAttribute non è attualmente utilizzata in .NET Framework, ma è riservato per un utilizzo futuro. ". Vedi: [link] ( msdn.microsoft.com/en-us/library/ms146064(v=vs.100).aspx ).
where T : Delegate
nell'IL sottostante ( proprietà simili o parametrizzate, ovvero indicizzatori, ecc., Ecc.).
what does System.Runtime.CompilerServices.SpecialName Attribute do?
- Viene utilizzato per contrassegnare i metodi prodotti da alcuni costrutti di convenienza speciali dei linguaggi .Net di alto livello: funzioni di accesso alle proprietà, funzioni di accesso agli eventi, costruttori, operatori, indicizzatori, ecc. A meno che il metodo IL non sia contrassegnato con specialname
non verrebbe visualizzato come proprietà / evento / costruttore e sarebbe semplicemente riconosciuto come un metodo normale. Contrassegnare manualmente i metodi con nome appropriato con questo attributo è solo eseguire manualmente una parte del lavoro del compilatore.
op_Exponent
metodo e contrassegnarlo con l' specialname
attributo.
Che ne dite di:
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 all'elemento derivato è utilizzare la riflessione nel costruttore. 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 sia possibile. Puoi farlo 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);
var auto =
è ancora di tiposedan
È così che ho risolto questo problema per i campi. Puoi fare la stessa iterazione attraverso le proprietà, se lo desideri. Potresti voler fare alcuni controlli per null
ecc. Ma questa è l'idea.
public static DerivedClass ConvertFromBaseToDerived<BaseClass, DerivedClass>(BaseClass baseClass)
where BaseClass : class, new()
where DerivedClass : class, BaseClass, new()
{
DerivedClass derived = (DerivedClass)Activator.CreateInstance(typeof(DerivedClass));
derived.GetType().GetFields().ToList().ForEach(field =>
{
var base_ = baseClass.GetType().GetField(field.Name).GetValue(baseClass);
field.SetValue(derived, base_);
});
return derived;
}
Puoi semplicemente serializzare l'oggetto di base in JSON e quindi deserializzarlo nell'oggetto derivato.
Non nel senso tradizionale ... Converti in Json, poi nel tuo oggetto e boom, fatto! Jesse sopra aveva pubblicato la risposta per primo, ma non ha utilizzato questi metodi di estensione che rendono il processo molto più semplice. Crea un paio di metodi di estensione:
public static string ConvertToJson<T>(this T obj)
{
return JsonConvert.SerializeObject(obj);
}
public static T ConvertToObject<T>(this string json)
{
if (string.IsNullOrEmpty(json))
{
return Activator.CreateInstance<T>();
}
return JsonConvert.DeserializeObject<T>(json);
}
Mettili nella tua cassetta degli attrezzi per sempre, quindi puoi sempre farlo:
var derivedClass = baseClass.ConvertToJson().ConvertToObject<derivedClass>();
Ah, il potere di JSON.
Ci sono un paio di trucchi con questo approccio: stiamo davvero creando un nuovo oggetto, non il casting, che può o non può avere importanza. I campi privati non verranno trasferiti, i costruttori con parametri non verranno chiamati, ecc. È possibile che qualche json figlio non venga assegnato. I flussi non sono gestiti in modo innato da JsonConvert. Tuttavia, se la nostra classe non si basa su campi e costruttori privati, questo è un metodo molto efficace per spostare i dati da una classe all'altra senza mappare e chiamare i costruttori, che è il motivo principale per cui vogliamo eseguire il cast in primo luogo.
No, vedi questa domanda che ho posto - Upcasting in .NET usando generici
Il modo migliore è creare un costruttore predefinito sulla classe, costruire e quindi chiamare un Initialise
metodo