Est-il possible d'affecter un objet de classe de base à une référence de classe dérivée avec un typage explicite en C #?.
Je l'ai essayé et cela crée une erreur d'exécution.
Est-il possible d'affecter un objet de classe de base à une référence de classe dérivée avec un typage explicite en C #?.
Je l'ai essayé et cela crée une erreur d'exécution.
Réponses:
Non. Une référence à une classe dérivée doit en fait faire référence à une instance de la classe dérivée (ou null). Sinon, comment vous attendriez-vous qu'il se comporte?
Par exemple:
object o = new object();
string s = (string) o;
int i = s.Length; // What can this sensibly do?
Si vous souhaitez pouvoir convertir une instance du type de base en type dérivé, je vous suggère d'écrire une méthode pour créer une instance de type dérivé appropriée. Ou regardez à nouveau votre arbre d'héritage et essayez de le repenser pour ne pas avoir à le faire en premier lieu.
Derived
, mais vous pouvez traiter une Derived
référence comme une Base
référence.
Base
, et l'autre crée une instance de Derived
. Si vous appelez une méthode virtuelle sur b
laquelle a été remplacée dans Derived
, vous verrez le Derived
comportement si vous avez une instance de Derived
. Mais il n'est pas vraiment approprié d'entrer dans les détails dans un fil de commentaires Stack Overflow - vous devriez vraiment lire un bon livre ou un didacticiel C #, car c'est assez fondamental.
Non, ce n'est pas possible car l'attribuer à une référence de classe dérivée reviendrait à dire "La classe de base est un substitut entièrement capable de la classe dérivée, elle peut faire tout ce que la classe dérivée peut faire", ce qui n'est pas vrai puisque les classes dérivées en offrent généralement plus de fonctionnalités que leur classe de base (du moins, c'est l'idée derrière l'héritage).
Vous pouvez écrire un constructeur dans la classe dérivée en prenant un objet de classe de base comme paramètre, en copiant les valeurs.
Quelque chose comme ça:
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
}
}
Dans ce cas, vous copiez l'objet de base et obtenez un objet de classe dérivé entièrement fonctionnel avec des valeurs par défaut pour les membres dérivés. De cette façon, vous pouvez également éviter le problème signalé par 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!
J'ai eu ce problème et je l'ai résolu en ajoutant une méthode qui prend un paramètre de type et convertit l'objet actuel dans ce type.
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;
}
Cela signifie que vous pouvez l'utiliser dans votre code comme ceci:
var base = new Base();
base.Data = 1;
var derived = base.As<Derived>();
Console.Write(derived.Data); // Would output 1
Comme beaucoup d'autres l'ont répondu, non.
J'utilise le code suivant dans les cas malheureux où j'ai besoin d'utiliser un type de base comme type dérivé. Oui, c'est une violation du principe de substitution de Liskov (LSP) et oui la plupart du temps, nous privilégions la composition à l'héritage. Accessoires à Markus Knappen Johansson dont la réponse originale est basée sur cela.
Ce code dans la classe de 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;
}
Permet:
derivedObject = baseObect.As<derivedType>()
Puisqu'il utilise la réflexion, il est "cher". Utilisez en conséquence.
user-defined conversions to or from a base class are not allowed
je vois les raisons de cela, mais je suis déçu, car cela aurait été tellement amusant si cela avait permis cela ..
if (type.BaseType != null)
déclaration relative au A. de Markus Knappen Johansson. Pourquoi? Cela signifie qu'il autoriserait un type dans les appels qui ne sont pas dérivés de MyBaseClass (ou quoi que ce soit d'ailleurs). Je me rends compte que cela provoquera toujours une erreur du compilateur si Assigné à myDerivedObject, mais s'il est juste utilisé comme Expression, il se compilera et au moment de l'exécution créera simplement un myDerivedObject sans aucune donnée copiée depuis "myBaseObject". Je ne peux pas imaginer un cas d'utilisation pour cela.
Aujourd'hui, j'ai rencontré le même problème et j'ai trouvé une solution simple et rapide au problème en utilisant JsonConvert
.
var base = new BaseClass();
var json = JsonConvert.SerializeObject(base);
DerivedClass derived = JsonConvert.DeserializeObject<DerivedClass>(json);
Comme tout le monde l'a dit ici, ce n'est pas possible directement.
La méthode que je préfère et qui est plutôt propre, consiste à utiliser un mappeur d'objets comme AutoMapper .
Il fera la tâche de copier automatiquement les propriétés d'une instance à une autre (pas nécessairement du même type).
Expansion sur la réponse de @ ybo - ce n'est pas possible car l'instance que vous avez de la classe de base n'est pas en fait une instance de la classe dérivée. Il ne connaît que les membres de la classe de base et ne sait rien de ceux de la classe dérivée.
La raison pour laquelle vous pouvez convertir une instance de la classe dérivée en une instance de la classe de base est que la classe dérivée est déjà en fait une instance de la classe de base, puisqu'elle a déjà ces membres. Le contraire ne peut être dit.
Vous pouvez convertir une variable typée comme classe de base en type d'une classe dérivée; cependant, par nécessité, cela effectuera une vérification à l'exécution, pour voir si l'objet réel impliqué est du type correct.
Une fois créé, le type d'un objet ne peut pas être modifié (notamment, il peut ne pas avoir la même taille). Cependant, vous pouvez convertir une instance, en créant une nouvelle instance du deuxième type, mais vous devez écrire le code de conversion manuellement.
Non ce n'est pas possible.
Considérez un scénario dans lequel un ACBus est une classe dérivée de la classe de base Bus. ACBus a des fonctionnalités telles que TurnOnAC et TurnOffAC qui fonctionnent sur un champ nommé ACState. TurnOnAC définit ACState sur on et TurnOffAC définit ACState sur off. Si vous essayez d'utiliser les fonctionnalités TurnOnAC et TurnOffAC sur Bus, cela n'a aucun sens.
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");
}
}
}
lorsque nous créons un objet de classe enfant, l'objet de classe de base est automatiquement lancé afin que la variable de référence de classe de base puisse pointer vers l'objet de classe enfant.
mais pas l'inverse car une variable de référence de classe enfant ne peut pas pointer vers un objet de classe de base car aucun objet de classe enfant n'est créé.
et notez également que la variable de référence de classe de base ne peut appeler que le membre de classe de base.
Il existe en fait un moyen de le faire. Pensez à la façon dont vous pourriez utiliser Newtonsoft JSON pour désérialiser un objet de json. Il ignorera (ou du moins pourra) les éléments manquants et peuplera tous les éléments dont il a connaissance.
Alors, voici comment je l'ai fait. Un petit exemple de code suivra mon explication.
Créez une instance de votre objet à partir de la classe de base et remplissez-la en conséquence.
À l'aide de la classe «jsonconvert» de Newtonsoft json, sérialisez cet objet dans une chaîne json.
Créez maintenant votre objet de sous-classe en désérialisant avec la chaîne json créée à l'étape 2. Cela créera une instance de votre sous-classe avec toutes les propriétés de la classe de base.
Ça fonctionne super bien! Alors ... quand est-ce utile? Certaines personnes ont demandé quand cela aurait du sens et ont suggéré de changer le schéma de l'OP pour tenir compte du fait que vous ne pouvez pas le faire nativement avec l'héritage de classe (dans .Net).
Dans mon cas, j'ai une classe de paramètres qui contient tous les paramètres "de base" d'un service. Les services spécifiques ont plus d'options et ceux-ci proviennent d'une autre table DB, donc ces classes héritent de la classe de base. Ils ont tous un ensemble d'options différent. Ainsi, lors de la récupération des données pour un service, il est beaucoup plus facile de remplir d'abord les valeurs à l'aide d'une instance de l'objet de base. Une méthode pour ce faire avec une seule requête DB. Juste après cela, je crée l'objet de sous-classe en utilisant la méthode décrite ci-dessus. Je fais ensuite une deuxième requête et remplis toutes les valeurs dynamiques sur l'objet de sous-classe.
La sortie finale est une classe dérivée avec toutes les options définies. Répéter ceci pour de nouvelles sous-classes supplémentaires ne prend que quelques lignes de code. C'est simple, et il utilise un package très éprouvé (Newtonsoft) pour faire fonctionner la magie.
Cet exemple de code est vb.Net, mais vous pouvez facilement le convertir en 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));
. Je n'utiliserais cela que pour les tests unitaires et autres "piratages" hors production!
Vous pouvez utiliser une extension:
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));
}
Dans du 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);
}
Ce n'est peut-être pas pertinent, mais j'ai pu exécuter du code sur un objet dérivé compte tenu de sa base. C'est définitivement plus piraté que je ne le souhaiterais, mais ça marche:
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);
Vous pouvez le faire en utilisant 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; }
}
Vous obtenez trois avantages en utilisant cette approche.
Je sais que c'est vieux mais je l'ai utilisé avec succès pendant un bon moment.
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);
}
}
J'ai combiné certaines parties des réponses précédentes (grâce à ces auteurs) et mis en place une classe statique simple avec deux méthodes que nous utilisons.
Oui, c'est simple, non ça ne couvre pas tous les scénarios, oui ça pourrait être élargi et amélioré, non ce n'est pas parfait, oui ça pourrait éventuellement être rendu plus efficace, non ce n'est pas la meilleure chose depuis le pain tranché, oui il y en a mappeurs d'objet de package nuget robustes complets qui sont bien meilleurs pour une utilisation intensive, etc.
Et bien sûr, il essaiera de mapper les valeurs de n'importe quel objet à n'importe quel objet, dérivé ou non (seules les propriétés publiques qui portent le même nom bien sûr - ignorent le reste).
USAGE:
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 UTILITAIRE STATIQUE:
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);
}
});
}
}
Vous pouvez utiliser un constructeur de copie qui appelle immédiatement le constructeur d'instance, ou si votre constructeur d'instance fait plus que des affectations, le constructeur de copie affecte les valeurs entrantes à l'instance.
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; }
}
Référence à la documentation Microsoft C # sous Constructor pour cet exemple ayant eu ce problème dans le passé.
Une autre solution consiste à ajouter une méthode d'extension comme ceci:
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;
}
}
puis avoir un constructeur dans chaque classe dérivée qui accepte la classe de base:
public class DerivedClass: BaseClass
{
public DerivedClass(BaseClass baseModel)
{
this.CopyProperties(baseModel);
}
}
Il écrasera également éventuellement les propriétés de destination si elles sont déjà définies (non nulles) ou non.
Est-il possible d'affecter un objet de classe de base à une référence de classe dérivée avec un typage explicite en C #?.
Des conversions non seulement explicites, mais également implicites sont possibles.
Le langage C # n'autorise pas de tels opérateurs de conversion, mais vous pouvez toujours les écrire en utilisant C # pur et ils fonctionnent. Notez que la classe qui définit l'opérateur de conversion implicite ( Derived
) et la classe qui utilise l'opérateur ( Program
) doivent être définies dans des assemblys séparés (par exemple, la Derived
classe est dans un library.dll
qui est référencé en program.exe
contenant 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.
}
}
Lorsque vous référencez la bibliothèque à l'aide de la référence de projet dans Visual Studio, VS affiche des gribouillis lorsque vous utilisez la conversion implicite, mais il se compile très bien. Si vous faites simplement référence au library.dll
, il n'y a pas de gribouillis.
System.Runtime.CompilerServices.SpecialName
attribut? La documentation pour chaque version de la plus ancienne disponible (2.0) à la "version actuelle" (4.6? "N'importe qui? N'importe qui?") Ne dit pas ce qu'elle fait, mais dit "La classe SpecialNameAttribute n'est pas actuellement utilisée dans le .NET Framework, mais est réservé pour une utilisation future. ". Voir: [lien] ( msdn.microsoft.com/en-us/library/ms146064(v=vs.100).aspx ).
where T : Delegate
les propriétés paramétrées ou les indexeurs, etc.).
what does System.Runtime.CompilerServices.SpecialName Attribute do?
- Il est utilisé pour marquer les méthodes produites par certaines constructions de commodité spéciales des langages .Net de haut niveau: accesseurs de propriété, accesseurs d'événements, constructeurs, opérateurs, indexeurs, etc. À moins que la méthode IL soit marquée, specialname
elle ne serait pas vue comme propriété / événement / constructeur et il serait simplement reconnu comme une méthode normale. Marquer manuellement des méthodes nommées de manière appropriée avec cet attribut ne fait qu'une partie du travail du compilateur.
op_Exponent
méthode et marquez-la avec l' specialname
attribut.
Que diriez-vous:
public static T As<T>(this object obj)
{
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj));
}
La meilleure façon d'ajouter toutes les propriétés de base à l'élément dérivé est d'utiliser la réflexion dans costructor. Essayez ce code, sans créer de méthodes ou d'instances.
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) { }
}
}
Je ne suis pas d'accord pour dire que ce n'est pas possible. Vous pouvez le faire comme ceci:
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;
}
Usage:
var sedan = new Sedan();
sedan.NumberOfDoors = 4;
var auto = ConvertAuto<Auto>(sedan);
var auto =
est toujours de typesedan
C'est ainsi que j'ai résolu ce problème pour les champs. Vous pouvez faire la même itération à travers les propriétés si vous le souhaitez. Vous voudrez peut-être faire quelques vérifications pour null
etc., mais c'est l'idée.
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;
}
Vous pouvez simplement sérialiser l'objet de base en JSON, puis le désérialiser en objet dérivé.
Pas dans le sens traditionnel ... Convertissez en Json, puis en votre objet, et boum, c'est fait! Jesse ci-dessus a publié la réponse en premier, mais n'a pas utilisé ces méthodes d'extension qui facilitent tellement le processus. Créez quelques méthodes d'extension:
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);
}
Mettez-les dans votre boîte à outils pour toujours, alors vous pouvez toujours le faire:
var derivedClass = baseClass.ConvertToJson().ConvertToObject<derivedClass>();
Ah, la puissance de JSON.
Il y a quelques pièges avec cette approche: nous créons vraiment un nouvel objet, pas un casting, qui peut ou non avoir une importance. Les champs privés ne seront pas transférés, les constructeurs avec des paramètres ne seront pas appelés, etc. Il est possible que certains enfants json ne soient pas affectés. Les flux ne sont pas gérés de manière innée par JsonConvert. Cependant, si notre classe ne s'appuie pas sur des champs et des constructeurs privés, il s'agit d'une méthode très efficace pour déplacer des données d'une classe à l'autre sans mappage ni appel de constructeurs, ce qui est la raison principale pour laquelle nous souhaitons effectuer un cast en premier lieu.
Non, voyez cette question que j'ai posée - Upcasting en .NET en utilisant des génériques
Le meilleur moyen est de créer un constructeur par défaut sur la classe, de construire puis d'appeler une Initialise
méthode