¿Es posible asignar un objeto de clase base a una referencia de clase derivada con un encasillado explícito en C #?
Lo he probado y crea un error en tiempo de ejecución.
c#
casting
derived-class
base-class
downcast
Maddy.Shik
fuente
fuente
Derived
, pero puede tratar unaDerived
referencia comoBase
referencia.Base
y el otro crea una instancia deDerived
. Si llama a un método virtual en elb
que se anulóDerived
, verá elDerived
comportamiento si tiene una instancia deDerived
. Pero no es realmente apropiado entrar en detalles en un hilo de comentarios de Stack Overflow; realmente debería leer un buen libro o tutorial de C #, ya que esto es algo bastante fundamental.No, eso no es posible ya que asignarlo a una referencia de clase derivada sería como decir "La clase base es un sustituto totalmente capaz de la clase derivada, puede hacer todo lo que puede hacer la clase derivada", lo cual no es cierto ya que las clases derivadas en general ofrecen más funcionalidad que su clase base (al menos, esa es la idea detrás de la herencia).
Podría escribir un constructor en la clase derivada tomando un objeto de clase base como parámetro, copiando los valores.
Algo como esto:
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 } }
En ese caso, copiaría el objeto base y obtendría un objeto de clase derivada completamente funcional con valores predeterminados para miembros derivados. De esta forma también puedes evitar el problema señalado por 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!
fuente
Tuve este problema y lo resolví agregando un método que toma un parámetro de tipo y convierte el objeto actual en ese 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; }
Eso significa que puede usarlo en su código de esta manera:
var base = new Base(); base.Data = 1; var derived = base.As<Derived>(); Console.Write(derived.Data); // Would output 1
fuente
Como muchos otros han respondido, No.
Utilizo el siguiente código en esas desafortunadas ocasiones en las que necesito usar un tipo base como tipo derivado. Sí, es una violación del principio de sustitución de Liskov (LSP) y sí, la mayoría de las veces favorecemos la composición sobre la herencia. Apoyos para Markus Knappen Johansson, cuya respuesta original se basa en esto.
Este código en la clase 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:
Dado que utiliza la reflexión, es "caro". Utilice en consecuencia.
fuente
user-defined conversions to or from a base class are not allowed
veo las razones de esto, pero estoy decepcionado, ya que hubiera sido muy divertido si permitiera esto ..if (type.BaseType != null)
declaración relativa a la A. de Markus Knappen Johansson. Eso significa que permitiría un Tipo en las Llamadas que no se Derive de MyBaseClass (ni nada para el caso). Me doy cuenta de que seguirá causando un error de compilación si está asignado a myDerivedObject, pero si solo se usa como una expresión, se compilará y, en tiempo de ejecución, simplemente creará un myDerivedObject sin ningún dato copiado de "myBaseObject". No puedo imaginar un caso de uso para eso.No, no es posible, de ahí su error de tiempo de ejecución.
Pero puede asignar una instancia de una clase derivada a una variable de tipo de clase base.
fuente
Solución con JsonConvert (en lugar de encasillado)
Hoy enfrenté el mismo problema y encontré una solución simple y rápida al problema usando
JsonConvert
.var base = new BaseClass(); var json = JsonConvert.SerializeObject(base); DerivedClass derived = JsonConvert.DeserializeObject<DerivedClass>(json);
fuente
Como dijeron todos aquí, eso no es posible directamente.
El método que prefiero y es bastante limpio, es usar un Object Mapper como AutoMapper .
Hará la tarea de copiar propiedades de una instancia a otra (no necesariamente del mismo tipo) automáticamente.
fuente
Ampliando la respuesta de @ ybo, no es posible porque la instancia que tiene de la clase base no es en realidad una instancia de la clase derivada. Solo conoce los miembros de la clase base y no sabe nada sobre los de la clase derivada.
La razón por la que puede convertir una instancia de la clase derivada a una instancia de la clase base es porque la clase derivada ya es una instancia de la clase base, ya que ya tiene esos miembros. No se puede decir lo contrario.
fuente
Puede convertir una variable que se escribe como la clase base al tipo de una clase derivada; sin embargo, esto necesariamente hará una verificación en tiempo de ejecución, para ver si el objeto real involucrado es del tipo correcto.
Una vez creado, el tipo de objeto no se puede cambiar (sobre todo, es posible que no tenga el mismo tamaño). Sin embargo, puede convertir una instancia, creando una nueva instancia del segundo tipo, pero debe escribir el código de conversión manualmente.
fuente
No, no es posible.
Considere un escenario en el que un ACBus es una clase derivada de un bus de clase base. ACBus tiene características como TurnOnAC y TurnOffAC que operan en un campo llamado ACState. TurnOnAC activa ACState y TurnOffAC desactiva ACState. Si intenta utilizar las funciones TurnOnAC y TurnOffAC en Bus, no tiene sentido.
fuente
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"); } }
}
cuando creamos un objeto de clase secundaria, el objeto de clase base se inicia automáticamente para que la variable de referencia de la clase base pueda apuntar al objeto de clase secundaria.
pero no al revés porque una variable de referencia de clase secundaria no puede apuntar al objeto de clase base porque no se crea ningún objeto de clase secundaria.
y también observe que la variable de referencia de la clase base solo puede llamar al miembro de la clase base.
fuente
De hecho, HAY una forma de hacer esto. Piense en cómo podría usar Newtonsoft JSON para deserializar un objeto de json. Ignorará (o al menos podrá) los elementos que faltan y completará todos los elementos que conozca.
Así que así es como lo hice. Una pequeña muestra de código seguirá mi explicación.
Cree una instancia de su objeto a partir de la clase base y rellénela en consecuencia.
Usando la clase "jsonconvert" de Newtonsoft json, serialice ese objeto en una cadena json.
Ahora cree su objeto de subclase deserializando con la cadena json creada en el paso 2. Esto creará una instancia de su subclase con todas las propiedades de la clase base.
¡Esto funciona de maravilla! Entonces ... ¿cuándo es esto útil? Algunas personas preguntaron cuándo tendría sentido esto y sugirieron cambiar el esquema del OP para adaptarse al hecho de que no puede hacer esto de forma nativa con la herencia de clases (en .Net).
En mi caso, tengo una clase de configuración que contiene todas las configuraciones "base" para un servicio. Los servicios específicos tienen más opciones y provienen de una tabla de base de datos diferente, por lo que esas clases heredan la clase base. Todos tienen un conjunto diferente de opciones. Por tanto, al recuperar los datos de un servicio, es mucho más fácil rellenar PRIMERO los valores utilizando una instancia del objeto base. Un método para hacer esto con una sola consulta de base de datos. Inmediatamente después de eso, creo el objeto de la subclase usando el método descrito anteriormente. Luego hago una segunda consulta y completo todos los valores dinámicos en el objeto de la subclase.
El resultado final es una clase derivada con todas las opciones establecidas. Repetir esto para nuevas subclases adicionales requiere solo unas pocas líneas de código. Es simple y usa un paquete muy probado (Newtonsoft) para hacer que la magia funcione.
Este código de ejemplo es vb.Net, pero puede convertirlo fácilmente a 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)
fuente
var destObject = JsonConvert.DeserializeObject<DestinationType>(JsonConvert.SerializeObject(srcObject));
. ¡Solo usaría esto para pruebas unitarias y otros "piratería" que no sean de producción!Puede utilizar una extensión:
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)); }
En codigo:
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); }
fuente
Puede que no sea relevante, pero pude ejecutar código en un objeto derivado dada su base. Definitivamente es más hacky de lo que me gustaría, pero 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);
fuente
Puede hacer esto 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; } }
Obtiene tres beneficios con este enfoque.
fuente
Sé que esto es antiguo, pero lo he usado con éxito durante bastante tiempo.
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); } }
fuente
Combiné algunas partes de las respuestas anteriores (gracias a esos autores) y armé una clase estática simple con dos métodos que estamos usando.
Sí, es simple, no, no cubre todos los escenarios, sí, podría expandirse y mejorarse, no, no es perfecto, sí, posiblemente podría hacerse más eficiente, no, no es lo mejor desde el pan de molde, sí, los hay mapeadores de objetos de paquetes nuget robustos y completos que son mucho mejores para un uso intensivo, etc., etc., yada yada, pero funciona para nuestras necesidades básicas :)
Y, por supuesto, intentará mapear valores de cualquier objeto a cualquier objeto, derivado o no (solo las propiedades públicas que tienen el mismo nombre, por supuesto, ignora el 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);
CLASE DE UTILIDAD ESTÁTICA:
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); } }); } }
fuente
Puede usar un constructor de copia que invoca inmediatamente al constructor de instancia, o si su constructor de instancia hace más que asignaciones, el constructor de copia asigna los valores entrantes a la instancia.
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; } }
Se hizo referencia a la documentación de Microsoft C # en Constructor para este ejemplo después de haber tenido este problema en el pasado.
fuente
Otra solución es agregar un método de extensión así:
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; } }
luego tenga un constructor en cada clase derivada que acepte la clase base:
public class DerivedClass: BaseClass { public DerivedClass(BaseClass baseModel) { this.CopyProperties(baseModel); } }
También sobrescribirá opcionalmente las propiedades de destino si ya están configuradas (no nulas) o no.
fuente
No solo son posibles conversiones explícitas, sino también implícitas.
El lenguaje C # no permite tales operadores de conversión, pero aún puede escribirlos usando C # puro y funcionan. Tenga en cuenta que la clase que define el operador de conversión implícito (
Derived
) y la clase que usa el operador (Program
) deben definirse en ensamblajes separados (por ejemplo, laDerived
clase está en una a lalibrary.dll
que se hace referencia alprogram.exe
contener laProgram
clase).//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. } }
Cuando hace referencia a la biblioteca usando la Referencia del proyecto en Visual Studio, VS muestra garabatos cuando usa la conversión implícita, pero se compila bien. Si solo hace referencia a
library.dll
, no hay garabatos.fuente
System.Runtime.CompilerServices.SpecialName
Attribute? Los documentos de cada versión, desde la más antigua disponible (2.0) hasta la "versión actual" (4.6? "¿Alguien? ¿Alguien?") No dicen lo que hace, pero sí dicen "La clase SpecialNameAttribute no se usa actualmente en .NET Framework, pero está reservado para uso futuro. ". Ver: [enlace] ( msdn.microsoft.com/en-us/library/ms146064(v=vs.100).aspx ).where T : Delegate
propiedades similares o parametrizadas, también conocidas como indexadores, etc., etc.).what does System.Runtime.CompilerServices.SpecialName Attribute do?
- Se usa para marcar los métodos producidos por algunas construcciones especiales de conveniencia de los lenguajes .Net de alto nivel: accesores de propiedad, accesores de eventos, constructores, operadores, indexadores, etc. A menos que el método IL esté marcado conspecialname
, no se vería como propiedad / evento / constructor y sería reconocido como un método normal. Marcar manualmente métodos con nombres apropiados con este atributo es simplemente hacer manualmente un poco del trabajo del compilador.op_Exponent
método y márquelo con elspecialname
atributo.Qué tal si:
public static T As<T>(this object obj) { return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj)); }
fuente
La mejor manera de agregar todas las propiedades base al elemento derivado es usar la reflexión en costructor. Pruebe este código, sin crear métodos o instancias.
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) { } } }
fuente
No estoy de acuerdo con que no sea posible. Puedes hacerlo así:
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);
fuente
var auto =
sigue siendo de tiposedan
Así es como resolví esto para los campos. Puede hacer la misma iteración a través de las propiedades si lo desea. Es posible que desee hacer algunas comprobaciones para,
null
etc., pero esta es la 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; }
fuente
Puede simplemente serializar el objeto base en JSON y luego deserializarlo en el objeto derivado.
fuente
No en el sentido tradicional ... Convierta a Json, luego a su objeto, ¡y listo! Jesse publicó primero la respuesta, pero no usó estos métodos de extensión que facilitan mucho el proceso. Crea un par de métodos de extensión:
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); }
Ponlos en tu caja de herramientas para siempre, entonces siempre podrás hacer esto:
var derivedClass = baseClass.ConvertToJson().ConvertToObject<derivedClass>();
Ah, el poder de JSON.
Hay un par de errores con este enfoque: Realmente estamos creando un nuevo objeto, no lanzando, lo que puede o no importar. Los campos privados no serán transferidos, los constructores con parámetros no serán llamados, etc. Es posible que no se asigne algún json hijo. JsonConvert no gestiona las transmisiones de forma innata. Sin embargo, si nuestra clase no se basa en campos y constructores privados, este es un método muy efectivo para mover datos de una clase a otra sin mapear y llamar a constructores, que es la razón principal por la que queremos lanzar en primer lugar.
fuente
No, vea esta pregunta que hice: Upcasting en .NET usando genéricos
La mejor manera es hacer un constructor predeterminado en la clase, construir y luego llamar a un
Initialise
métodofuente