web-dev-qa-db-fra.com

Est-il possible d'affecter un objet de classe de base à une référence de classe dérivée avec une conversion de type explicite?

Est-il possible d'affecter un objet de classe de base à une référence de classe dérivée avec une conversion de type explicite en C # ?.

Je l'ai essayé et cela crée une erreur d'exécution.

68
Maddy.Shik

Non. Une référence à une classe dérivée doit en réalité faire référence à une instance de la classe dérivée (ou null). Sinon, comment vous attendez-vous à ce 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 revoir pour que vous n'ayez pas besoin de le faire en premier lieu.

81
Jon Skeet

Non, ce n'est pas possible car l'affecter à une référence de classe dérivée équivaudrait à dire "La classe de base est un substitut parfaitement capable pour 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 général 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 en tant que 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, copiez l'objet de base et obtenez un objet de classe dérivée entièrement fonctionnel avec les 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();
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!
40
Michael Klement

J'ai eu ce problème et 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 ont répondu, non.

J'utilise le code suivant dans les cas malheureux où j'ai besoin d'utiliser un type de base en tant que 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 à la succession. Des accessoires à Markus Knappen Johansson dont la réponse originale est basée sur.

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

Comme il utilise la réflexion, il est "cher". Utilisez en conséquence.

8
MEC

Non, ce n'est pas possible, d'où votre erreur d'exécution.

Mais vous pouvez affecter une instance d'une classe dérivée à une variable de type classe de base.

5
ybo

Vous pouvez convertir une variable variable qui est typée en tant que classe de base sur le type d'une classe dérivée; cependant, par nécessité, cela fera une vérification à l'exécution, pour voir si l'objet réellement impliqué est du type correct.

Une fois créé, le type d'un objet ne peut plus être modifié (notamment, sa taille peut être différente). Cependant, vous pouvez convertir une instance, en créant une nouvelle instance du second type - mais vous devez écrire le code de conversion manuellement.

3
Marc Gravell

Comme tout le monde ici l'a dit, 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 se chargera de copier automatiquement les propriétés d'une instance à une autre (pas nécessairement du même type).

3
Mahmoodvcs

Non ce n'est pas possible.

Prenons un scénario dans lequel un bus AC est une classe dérivée de la classe de base Bus. ACBus possède des fonctionnalités telles que TurnOnAC et TurnOffAC qui fonctionnent sur un champ nommé ACState. TurnOnAC active ACState et TurnOffAC désactive ACState. Si vous essayez d'utiliser les fonctionnalités TurnOnAC et TurnOffAC sur le bus, cela n'a aucun sens.

2
Rohit Dodle

Il existe en fait IS un moyen de le faire. Réfléchissez à la manière dont vous pourriez utiliser Newtonsoft JSON pour désérialiser un objet à partir de json. Il ignorera (ou du moins pourra) ignorer les éléments manquants et peuplera tous les éléments qu’il connaît.

Alors voici comment je l'ai fait. Un petit exemple de code suivra mon explication.

  1. Créez une instance de votre objet à partir de la classe de base et remplissez-la en conséquence.

  2. À l'aide de la classe "jsonconvert" de Newtonsoft json, sérialisez cet objet dans une chaîne json.

  3. Créez maintenant votre objet de classe secondaire en désérialisant avec la chaîne json créée à l'étape 2. Cela créera une instance de votre classe secondaire 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 du PO pour tenir compte du fait que vous ne pouvez pas le faire de manière native avec l'héritage de classe (en .Net).

Dans mon cas, j'ai une classe de paramètres qui contient tous les paramètres "de base" d'un service. Des services spécifiques ont plus d'options et celles-ci proviennent d'une autre table de base de données. Ces classes héritent donc de la classe de base. Ils ont tous un ensemble différent d'options. Ainsi, lors de la récupération des données d'un service, il est beaucoup plus facile de PREMIER peupler les valeurs à l'aide d'une instance de l'objet de base. Une méthode pour le faire avec une requête DB unique. Juste après, je crée l'objet de classe secondaire à l'aide de 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 la sous-classe.

La sortie finale est une classe dérivée avec toutes les options définies. Répéter cette opération pour de nouvelles sous-classes supplémentaires ne nécessite que quelques lignes de code. C'est simple, et il utilise un package très éprouvé et testé (Newtonsoft) pour faire fonctionner la magie.

Cet exemple de code est vb.Net, mais vous pouvez facilement 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)
2
Mark Sauer

Développer la réponse de @ ybo - ce n'est pas possible car l'instance de la classe de base que vous avez n'est pas en réalité 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.

Si vous pouvez convertir une instance de la classe dérivée en instance de la classe de base, c'est parce que la classe dérivée est déjà une instance de la classe de base, car elle possède déjà ces membres. On ne peut pas dire le contraire.

2
Andy
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, celui-ci est lancé automatiquement afin que la variable de référence de classe puisse pointer sur cet objet.

mais pas l'inverse, car une variable de référence de classe enfant ne peut pas pointer sur 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 peut uniquement appeler un membre de classe de base.

1
shatrudhan kumar

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

}
1
Ricardo Figueiredo

Solution avec JsonConvert (au lieu de transtypage)

Aujourd'hui, j'ai fait face au même problème et j'ai trouvé une solution simple et solution rapide au problème en utilisant JsonConvert.

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

Je sais que c'est vieux, mais je l'utilise avec succès depuis un certain temps. 

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

Vous pouvez le faire en utilisant générique.

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

Cette approche vous procure trois avantages.

  1. Vous ne dupliquez pas le code
  2. Vous n'utilisez pas de réflexion (ce qui est lent)
  3. Toutes vos conversions sont au même endroit
0
adeel41

Le meilleur moyen d’ajouter toutes les propriétés de base à un élément dérivé est d’utiliser la réflexion dans le constructeur. 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) { }
        }

    }
0
Uraitz

Peut-être pas pertinent, mais j’ai pu exécuter du code sur un objet dérivé en fonction de sa base. C'est certainement plus hacky que je le voudrais, 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);
0
tstone2077

Je ne suis pas d'accord pour dire que ce n'est pas possible. Vous pouvez le faire comme ça:

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);
0
Buzz Wilder

Une autre solution consiste à ajouter une méthode d'extension de la manière suivante:

 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 remplacera également éventuellement les propriétés de destination s'il est déjà défini (non nul) ou non.

0
d.popov

Est-il possible d'affecter un objet de classe de base à une référence de classe dérivée avec une conversion de type explicite en C # ?.

Des conversions non seulement explicites, mais aussi implicites sont possibles.

Le langage C # n'autorise pas de tels opérateurs de conversion, mais vous pouvez toujours les écrire en pur C # 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 assemblages séparés (par exemple, la classe Derived est dans un library.dll qui est référencé par program.exe contenant 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.
    }
}

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 elle se compile parfaitement. Si vous faites simplement référence au library.dll, il n'y a pas de gribouillis.

0
Ark-kun

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, il ne couvre pas tous les scénarios, oui, il pourrait être étendu et amélioré, non, ce n'est pas parfait, oui, il pourrait éventuellement être plus efficace, non, ce n'est pas la meilleure chose depuis le pain en tranches, oui, il y en a Des mappeurs d'objet de package de nuget robustes complets qui sont bien meilleurs pour une utilisation intensive, etc. etc., yada yada - mais cela fonctionne pour nos besoins de base bien que :) 

Et bien sûr, il va essayer de mapper les valeurs de n'importe quel objet sur n'importe quel objet, dérivé ou non (seules les propriétés publiques qui portent le même nom sont bien sûr ignorées par le reste).

UTILISATION:

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

Que diriez-vous:

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