web-dev-qa-db-fra.com

Une façon de convertir un type de base en un type dérivé

Je ne sais pas si c'est une chose étrange à faire ou non, ou si c'est une odeur de code ... mais je me demandais s'il y avait un moyen (une sorte de modèle de oop serait sympa) de "lancer" un type de base à une forme de son type dérivé. Je sais que cela n'a pas de sens car le type dérivé aura des fonctionnalités supplémentaires que le parent n'offre pas, ce qui en soi n'est pas fondamentalement sain. Mais existe-t-il un moyen de le faire? Voici un exemple de code pour que je puisse mieux expliquer ce que je demande.

public class SomeBaseClass {
    public string GetBaseClassName {get;set;}
    public bool BooleanEvaluator {get;set;}
}

public class SomeDerivedClass : SomeBaseClass {
    public void Insert(SqlConnection connection) {
          //...random connection stuff
          cmd.Parameters["IsItTrue"].Value = this.BooleanEvalutar;
          //...
    }
}

public static void Main(object[] args) {
    SomeBaseClass baseClass = new SomeBaseClass();
    SomeDerivedClass derClass = (SomeDerivedClass)baseClass; 
    derClass.Insert(new sqlConnection());
}

Je sais que cela semble maladroit, mais existe-t-il un moyen d'accomplir quelque chose de ce genre?

45
Adam Driscoll

Pas bien, dans les langues "managées". C'est downcasting, et il n'y a aucun moyen sensé de le gérer, pour exactement la raison que vous avez décrite (les sous-classes fournissent plus que les classes de base - d'où vient ce "plus"?). Si vous voulez vraiment un comportement similaire pour une hiérarchie particulière, vous pouvez utiliser des constructeurs pour les types dérivés qui prendront le type de base comme prototype.

On pourrait construire quelque chose avec une réflexion qui a géré les cas simples (types plus spécifiques qui n'ont pas d'état d'addition). En général, il suffit de reconcevoir pour éviter le problème.

Edit: Woops, ne peut pas écrire d'opérateurs de conversion entre les types de base/dérivés. Une bizarrerie de Microsoft essayant de "vous protéger" contre vous-même. Eh bien, au moins, ils ne sont pas aussi mauvais que Sun.

31
Adam Wright

Essayez la composition au lieu de l'héritage!

Il me semble que vous feriez mieux de passer une instance de SomeBaseClass à SomeDerivedClass (qui ne dérivera plus la classe de base et devrait être renommée comme telle)

public class BooleanHolder{       
    public bool BooleanEvaluator {get;set;}
}

public class DatabaseInserter{
    BooleanHolder holder;

    public DatabaseInserter(BooleanHolder holder){
        this.holder = holder;
    }

    public void Insert(SqlConnection connection) {
          ...random connection stuff
          cmd.Parameters["IsItTrue"].Value = holder.BooleanEvalutar;
          ...
    }
}

public static void Main(object[] args) {
    BooleanHolder h = new BooleanHolder();
    DatabaseInserter derClass = new DatabaseInserter(h);
    derClass.Insert(new sqlConnection);
}

Découvrez http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html (page 3):

Réutilisation de code via la composition La composition fournit un moyen alternatif pour Apple de réutiliser l'implémentation de peel () par Fruit. Au lieu d'étendre Fruit, Apple peut contenir une référence à un Instance du fruit et définir sa propre méthode peel () qui appelle simplement peel () sur le fruit.

18
Rob Fonseca-Ensor

Personnellement, je ne pense pas que cela vaille la peine d'utiliser l'héritage dans ce cas. Au lieu de cela, passez simplement l'instance de classe de base dans le constructeur et accédez-y via une variable membre.

private class ExtendedClass //: BaseClass - like to inherit but can't
{
    public readonly BaseClass bc = null;
    public ExtendedClass(BaseClass b)
    {
        this.bc = b;
    }

    public int ExtendedProperty
    {
        get
        {
        }
    }
}
15
Rob Sedgwick

Le downcasting est logique, si vous avez un objet de classe dérivée mais qu'il est référencé par une référence de type de classe de base et pour une raison quelconque, vous voulez qu'il soit référencé par une référence de type de classe dérivée. En d'autres termes, vous pouvez abaisser pour inverser l'effet de l'upcast précédent. Mais vous ne pouvez pas avoir un objet de classe de base référencé par une référence d'un type de classe dérivé.

9
Maciej Hehl

Je ne dis pas que je recommande cela. Mais vous pouvez transformer la classe de base en chaîne JSON, puis la convertir en classe dérivée.

SomeDerivedClass layer = JsonConvert.DeserializeObject<SomeDerivedClass>(JsonConvert.SerializeObject(BaseClassObject));
7
Donny V.

Non, ce n'est pas possible. Dans un langage géré comme C #, cela ne fonctionnera tout simplement pas. Le runtime ne le permettra pas, même si le compilateur le laisse passer.

Vous avez dit vous-même que cela semble maladroit:

SomeBaseClass class = new SomeBaseClass();
SomeDerivedClass derClass = (SomeDerivedClass)class; 

Alors demandez-vous si class est en fait une instance de SomeDerivedClass? Non, la conversion n'a donc aucun sens. Si vous devez convertir SomeBaseClass en SomeDerivedClass, vous devez fournir une sorte de conversion, soit un constructeur, soit une méthode de conversion.

Il semble cependant que votre hiérarchie de classe ait besoin d'un peu de travail. En général, il ne devrait pas être possible de convertir une instance de classe de base en une instance de classe dérivée. Il devrait généralement y avoir des données et/ou des fonctionnalités qui ne s'appliquent pas à la classe de base. Si la fonctionnalité de classe dérivée s'applique aux instances toutes de la classe de base, elle doit alors être cumulée dans la classe de base ou extraite dans une nouvelle classe qui ne fait pas partie de la hiérarchie des classes de base.

5
Derek Park

Le langage C # ne permet pas de tels opérateurs, mais vous pouvez toujours les écrire et ils fonctionnent:

[System.Runtime.CompilerServices.SpecialName]
public static Derived op_Implicit(Base a) { ... }

[System.Runtime.CompilerServices.SpecialName]
public static Derived op_Explicit(Base a) { ... }
5
Ark-kun

Oui - c'est une odeur de code, et cloue à peu près le fait que votre chaîne d'héritage est rompue.

Mon devinez (à partir de l'échantillon limité) est que vous préférez que DerivedClass fonctionne sur une instance de SomeBaseClass - de sorte que "DerivedClass a un SomeBaseClass", plutôt que "DerivedClass" est un SomeBaseClass ". C'est ce qu'on appelle "privilégier la composition à l'héritage".

1
Mark Brackett

Avez-vous pensé à une interface qui implémenterait actuellement votre classe de base et votre classe dérivée? Je ne connais pas les détails de la raison pour laquelle vous implémentez cette méthode, mais cela pourrait fonctionner.

1
marr75

Comme d'autres l'ont noté, le casting que vous proposez n'est pas vraiment possible. Serait-ce peut-être un cas où le motif décorateur (extrait Head First) peut être introduit?

1
Boris Callens

Ce n'est pas possible car comment allez-vous obtenir le "supplément" que possède la classe dérivée. Comment le compilateur saurait-il que vous voulez dire dérivéClasse1 et non dérivéClasse2 lorsque vous l'instanciez?

Je pense que ce que vous cherchez vraiment est le modèle d'usine ou similaire afin que vous puissiez instancier des objets sans vraiment connaître le type explicite qui est instancié. Dans votre exemple, avoir la méthode "Insert" serait une interface que l'instance retourne implémente.

0
Laplie Anderson

Comme de nombreuses réponses l'ont souligné, vous ne pouvez pas abattre ce qui est totalement logique.

Cependant, dans votre cas, SomeDerivedClass n'a pas de propriétés qui seront "manquantes". Vous pouvez donc créer une méthode d'extension comme celle-ci:

public static T ToDerived<T>(this SomeBaseClass baseClass) 
    where T:SomeBaseClass, new()
{
    return new T()
    {
        BooleanEvaluator = baseClass.BooleanEvaluator,
        GetBaseClassName = baseClass.GetBaseClassName
    };
}

Donc, vous ne lancez pas, vous convertissez simplement:

SomeBaseClass b = new SomeBaseClass();
SomeDerivedClass c = b.ToDerived<SomeDerivedClass>();

Cela ne fonctionne vraiment que si toutes les données de la classe de base se présentent sous la forme de propriétés lisibles et inscriptibles.

0
johnnycardy

Je ne sais pas pourquoi personne n'a dit cela et j'ai peut-être manqué quelque chose, mais vous pouvez utiliser le mot clé as et si vous devez faire une instruction if, utilisez if.

SomeDerivedClass derClass = class as SomeDerivedClass; //derClass is null if it isnt SomeDerivedClass
if(class is SomeDerivedClass)
    ;

-edit- j'ai posé cette question il y a longtemps

0
user34537

Ça ne peut pas marcher. Allez voir la page d'aide liée par l'erreur de compilation.

La meilleure solution consiste à utiliser des méthodes d'usine ici.

0
leppie

C'est ce qu'on appelle le downcasting et la suggestion de Seldaek d'utiliser la version "sûre" est valable.

Voici un assez bon description avec des exemples de code .

0
Ben Hoffstein

J'ai récemment eu besoin d'étendre un DTO simple avec un type dérivé afin d'y mettre plus de propriétés. J'ai ensuite voulu réutiliser certaines logiques de conversion que j'avais, des types de bases de données internes aux DTO.

J'ai résolu le problème en appliquant un constructeur vide sur les classes DTO, en l'utilisant comme ceci:

class InternalDbType {
    public string Name { get; set; }
    public DateTime Date { get; set; }
    // Many more properties here...
}

class SimpleDTO {
    public string Name { get; set; }
    // Many more properties here...
}

class ComplexDTO : SimpleDTO {
    public string Date { get; set; }
}

static class InternalDbTypeExtensions {
    public static TDto ToDto<TDto>(this InternalDbType obj) where TDto : SimpleDTO, new() {
        var dto = new TDto {
            Name = obj.Name
        }
    }
}

Je peux ensuite réutiliser la logique de conversion du DTO simple lors de la conversion en un DTO complexe. Bien sûr, je vais devoir remplir les propriétés du type complexe d'une autre manière, mais avec beaucoup, beaucoup de propriétés du simple DTO, cela simplifie vraiment les choses IMO.

0
lbergnehr