72 votes

Opérateur de conversion défini par l'utilisateur à partir de la classe de base

Introduction

Je suis conscient que "les conversions définies par l'utilisateur vers ou depuis une classe de base ne sont pas autorisées". MSDN donne, comme explication à cette règle, "Vous n'avez pas besoin de cet opérateur".

Je comprends qu'une conversion définie par l'utilisateur à une classe de base n'est pas nécessaire, car cela est évidemment fait implicitement. Cependant, j'ai besoin d'une conversion de une classe de base.

Dans ma conception actuelle, une enveloppe de code non géré, j'utilise un pointeur, stocké dans une classe Entity. Toutes les classes utilisant un pointeur dérivent de cette classe Entity, par exemple, une classe Body.

J'ai donc :

Méthode A

class Entity
{
    IntPtr Pointer;

    Entity(IntPtr pointer)
    {
        this.Pointer = pointer;
    }
}

class Body : Entity
{
    Body(IntPtr pointer) : base(pointer) { }

    explicit operator Body(Entity e)
    {
        return new Body(e.Pointer);
    }
}

Cette distribution est la plus illégale. (Notez que je n'ai pas pris la peine d'écrire les accesseurs). Sans lui, le compilateur sera me permettre de le faire :

Méthode B

(Body)myEntity
...

Cependant, au moment de l'exécution, j'obtiendrai une exception indiquant que cette distribution est impossible.

Conclusion

Par conséquent, j'ai besoin d'une conversion définie par l'utilisateur. de une classe de base, et C# me le refuse. En utilisant la méthode A, le compilateur se plaindra mais le code fonctionnera logiquement à l'exécution. En utilisant la méthode B, le compilateur ne se plaindra pas mais le code échouera évidemment à l'exécution.

Ce que je trouve étrange dans cette situation, c'est que MSDN me dit que je n'ai pas besoin de cet opérateur, et que le compilateur agit comme suit comme si c'était possible implicitement (méthode B). Que dois-je faire ?

Je suis conscient que je peux utiliser :

Solution A

class Body : Entity
{
    Body(IntPtr pointer) : base(pointer) { }

    static Body FromEntity(Entity e)
    {
        return new Body(e.Pointer);
    }
}

Solution B

class Body : Entity
{
    Body(IntPtr pointer) : base(pointer) { }

    Body(Entity e) : base(e.Pointer) { }
}

Solution C

class Entity
{
    IntPtr Pointer;

    Entity(IntPtr pointer)
    {
        this.Pointer = pointer;
    }

    Body ToBody()
    {
        return new Body(this.Pointer);
    }
}

Mais honnêtement, toutes les syntaxes pour ceux-ci sont horribles et devraient en fait être des casts. Donc, un moyen de faire fonctionner les casts ? Est-ce un défaut de conception du C# ou ai-je manqué une possibilité ? C'est comme si C# ne me faisait pas assez confiance pour écrire ma propre conversion base-enfant en utilisant leur système de cast.

47voto

Jon Skeet Points 692016

Ce n'est pas un défaut de conception. Voici pourquoi :

Entity entity = new Body();
Body body = (Body) entity;

Si vous étiez autorisé à écrire votre propre conversion définie par l'utilisateur ici, il y aurait deux conversions valides : une tentative de faire simplement un cast normal (qui est une conversion de référence, préservant l'identité) et votre conversion définie par l'utilisateur.

Lequel doit être utilisé ? Voulez-vous vraiment veulent que ceux-ci fassent des choses différentes ?

// Reference conversion: preserves identity
Object entity = new Body();
Body body = (Body) entity;

// User-defined conversion: creates new instance
Entity entity = new Body();
Body body = (Body) entity;

Yuk ! La folie est de ce côté, IMO. N'oubliez pas que le compilateur décide de ceci à temps de compilation en se basant uniquement sur le temps de compilation les types d'expressions concernées.

Personnellement, j'opterais pour la solution C - et peut-être même en faire une méthode virtuelle. De cette façon Body podría l'écraser pour qu'il ne renvoie que this si vous voulez qu'elle préserve l'identité. si possible mais en créant un nouvel objet si nécessaire.

20voto

Igor Zevaka Points 32586

Eh bien, quand tu fais le casting Entity a Body vous ne l'êtes pas. vraiment l'un à l'autre, mais plutôt le IntPtr à une nouvelle entité.

Pourquoi ne pas créer un opérateur de conversion explicite de IntPtr ?

public class Entity {
    public IntPtr Pointer;

    public Entity(IntPtr pointer) {
        this.Pointer = pointer;
    }
}

public class Body : Entity {
    Body(IntPtr pointer) : base(pointer) { }

    public static explicit operator Body(IntPtr ptr) {
        return new Body(ptr);
    }

    public static void Test() {
        Entity e = new Entity(new IntPtr());
        Body body = (Body)e.Pointer;
    }
}

9voto

robyaw Points 1321

Vous devriez utiliser votre solution B (l'argument du constructeur) ; voici d'abord pourquoi no d'utiliser les autres solutions proposées :

  • La solution A n'est qu'un habillage de la solution B ;
  • La solution C est tout simplement fausse (pourquoi une classe de base devrait-elle savoir comment se convertir elle-même en une sous-classe quelconque ?)

De même, si le Body devait contenir des propriétés supplémentaires, à quoi devraient-elles être initialisées lorsque vous effectuez votre distribution ? Il est de loin préférable d'utiliser le constructeur et d'initialiser les propriétés de la sous-classe, comme le veut la convention des langages OO.

2voto

siride Points 36602

La raison pour laquelle vous ne pouvez pas le faire est que ce n'est pas sûr dans le cas général. Considérez les possibilités. Si vous voulez pouvoir le faire parce que la classe de base et la classe dérivée sont interchangeables, alors vous n'avez vraiment qu'une seule classe et vous devriez fusionner les deux. Si vous voulez avoir votre opérateur de cast pour la commodité de pouvoir downcaster la base vers la dérivée, alors vous devez considérer que toutes les variables typées comme classe de base ne pointeront pas vers une instance de la classe dérivée spécifique vers laquelle vous essayez de la castée. Il pourrait mais il faudrait d'abord vérifier, ou risquer une exception de cast invalide. C'est pourquoi le downcasting est généralement désapprouvé et ceci n'est rien de plus que du downcasting en drag. Je vous suggère de repenser votre conception.

2voto

Jaroslav Jandek Points 5500

Pourquoi pas :

public class Entity {...}

public class Body : Entity
{
  public Body(Entity sourceEntity) { this.Pointer = sourceEntity.Pointer; }
}

donc dans le code que vous n'avez pas à écrire :

Body someBody = new Body(previouslyUnknownEntity.Pointer);

mais vous pouvez utiliser

Body someBody = new Body(previouslyUnknownEntity);

à la place.

C'est juste un changement cosmétique Je sais, mais c'est assez clair et vous pouvez changer les internes facilement. Il est également utilisé dans un modèle de wrapper dont j'ai oublié le nom (pour des objectifs légèrement différents).
Il est également clair que vous créez une nouvelle entité à partir d'une entité fournie, ce qui ne devrait pas prêter à confusion comme le ferait un opérateur/une conversion.

Note : je n'ai pas utilisé de compilateur, donc la possibilité d'une erreur de frappe existe.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X