web-dev-qa-db-fra.com

Meilleure façon de créer une instance d'objet enfant à partir d'objet parent

Je crée un objet enfant à partir d'un objet parent. Le scénario est donc que j'ai un objet et un objet enfant qui ajoute une propriété de distance pour les scénarios dans lesquels je souhaite effectuer une recherche. J'ai choisi d'utiliser l'héritage car mon interface utilisateur fonctionne de manière équivalente avec un objet de recherche ou une liste d'objets non le résultat d'une recherche d'emplacement. Donc, dans ce cas, l'héritage semble un choix judicieux. 

Dans l'état actuel des choses, je dois générer un nouvel objet MyObjectSearch à partir d'une instance de MyObject. Actuellement, je le fais manuellement dans le constructeur en définissant les propriétés une par une. Je pourrais utiliser la réflexion mais ce serait lent. Existe-t-il un meilleur moyen de réaliser ce type d’amélioration d’objet?

Espérons que mon code ci-dessous illustre le scénario.

public class MyObject {

    // Some properties and a location.
}

public class MyObjectSearch : MyObject {

    public double Distance { get; set; }

    public MyObjectSearch(MyObject obj) {
         base.Prop1 = obj.Prop1;
         base.Prop2 = obj.Prop2;
    }
}

Et ma fonction de recherche:

public List<MyObjectSearch> DoSearch(Location loc) { 
  var myObjectSearchList = new List<MyObjectSearch>();       

   foreach (var object in myObjectList) {
       var distance = getDistance();
       var myObjectSearch = new MyObjectSearch(object);
       myObjectSearch.Distance = distance;
       myObjectSearchList.add(myObjectSearch);
   } 
   return myObjectSearchList;

}

19
Captain John

La classe de base doit définir un constructeur de copie:

public class MyObject
{
    protected MyObject(MyObject other)
    {
        this.Prop1=other.Prop1;
        this.Prop2=other.Prop2;
    }

    public object Prop1 { get; set; }
    public object Prop2 { get; set; }
}

public class MyObjectSearch : MyObject
{

    public double Distance { get; set; }

    public MyObjectSearch(MyObject obj)
         : base(obj)
    {
        this.Distance=0;
    }
    public MyObjectSearch(MyObjectSearch other)
         : base(other)
    {
        this.Distance=other.Distance;
    }
}

De cette manière, la définition des propriétés est gérée pour toutes les classes dérivées par la classe de base.

25
ja72

Malheureusement, il n’ya pas de moyen facile de le faire. Comme vous l'avez dit, vous devez soit utiliser la réflexion, soit créer une méthode "Clone" qui générerait un nouvel objet enfant en utilisant un objet parent comme entrée, comme suit:

public class MyObjectSearch : MyObject {

    // Other code

    public static MyObjectSearch CloneFromMyObject(MyObject obj)
    {
        var newObj = new MyObjectSearch();

        // Copy properties here
        obj.Prop1 = newObj.Prop1;

        return newObj;
    }
}

Quoi qu'il en soit, vous finirez par écrire du code de réflexion (ce qui est lent) ou par écrire chaque propriété à la main. Tout dépend si vous voulez ou non la maintenabilité (réflexion) ou la vitesse (copie manuelle des propriétés).

4
qJake

Vous pouvez utiliser la réflexion pour copier les propriétés.

public class ChildClass : ParentClass
{


    public ChildClass(ParentClass ch)
    {
        foreach (var prop in ch.GetType().GetProperties())
        {
            this.GetType().GetProperty(prop.Name).SetValue(this, prop.GetValue(ch, null), null);
        }
    }
}
0
mehdi

Une solution générique consisterait à le sérialiser en json et retour. Dans la chaîne json, il n'y a aucune information sur le nom de la classe à partir de laquelle elle a été sérialisée. La plupart des gens le font en javascript. 

Comme vous le voyez, cela fonctionne bien pour les objets pocco mais je ne garantis pas que cela fonctionne dans tous les cas complexes. Mais cela se produit pour les classes non héritées lorsque les propriétés sont mises en correspondance.

using Newtonsoft.Json;

namespace CastParentToChild
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var p = new parent();
            p.a=111;
            var s = JsonConvert.SerializeObject(p);
            var c1 = JsonConvert.DeserializeObject<child1>(s);
            var c2 = JsonConvert.DeserializeObject<child2>(s);

            var foreigner = JsonConvert.DeserializeObject<NoFamily>(s);

            bool allWorks = p.a == c1.a && p.a == c2.a && p.a == foreigner.a;
            //Your code goes here
            Console.WriteLine("Is convertable: "+allWorks + c2.b);
        }
    }

    public class parent{
        public int a;
    }

    public class child1 : parent{
     public int b=12345;   
    }

    public class child2 : child1{
    }

    public class NoFamily{
        public int a;
        public int b = 99999;
    }

    // Is not Deserializeable because
    // Error 'NoFamily2' does not contain a definition for 'a' and no extension method 'a' accepting a first argument of type 'NoFamily2' could be found (are you missing a using directive or an Assembly reference?)
    public class NoFamily2{
        public int b;
    }
}
0
Reiner

Il semble naturel que l'objet de base ait un constructeur avec des paramètres pour ses propriétés:

public class MyObject 
{
    public MyObject(prop1, prop2, ...)
    {
        this.Prop1 = prop1;
        this.Prop2 = prop2;
    }
}

Alors, dans votre objet descendant, vous pouvez avoir:

public MyObjectSearch(MyObject obj)
    :base(obj.Prop1, obj.Prop2)

Cela réduit la duplication liée aux affectations. Vous pouvez utiliser la réflexion pour copier automatiquement toutes les propriétés, mais cette méthode semble plus lisible. 

Notez également que si vos classes ont tellement de propriétés que vous envisagez d'automatiser leur copie, elles risquent de violer le Principe de responsabilité unique et vous devriez plutôt envisager de modifier votre conception.

0
BartoszKP

Il existe des bibliothèques pour gérer cela; mais si vous voulez juste une implémentation rapide dans quelques endroits, je choisirais certainement un "constructeur de copie", comme suggéré précédemment.

Un point intéressant non mentionné est que si un objet est une sous-classe, il peut alors accéder aux variables privées de l'enfant de l'intérieur du parent!

Donc, sur le parent, ajoutez une méthode CloneIntoChild. Dans mon exemple:

  • Order est la classe parente
  • OrderSnapshot est la classe enfant
  • _bestPrice est un membre privé non en lecture seule sur Order. Mais Order peut le définir pour OrderSnapshot.

Exemple:

public OrderSnapshot CloneIntoChild()
{
    OrderSnapshot sn = new OrderSnapshot()
    {
        _bestPrice = this._bestPrice,
        _closed = this._closed,
        _opened = this._opened,
        _state = this._state       
    };
    return sn;
}

REMARQUE: les variables de membre en lecture seule DOIVENT être définies dans le constructeur. Vous devrez donc utiliser le constructeur enfant pour les définir.

Bien que je n'aime pas les "modifications" en général, j'utilise beaucoup cette approche pour les instantanés analytiques ... 

0
James Joyce