web-dev-qa-db-fra.com

Mon utilisation de l'opérateur de casting explicite est-elle raisonnable ou un mauvais hack?

J'ai un gros objet:

class BigObject{
    public int Id {get;set;}
    public string FieldA {get;set;}
    // ...
    public string FieldZ {get;set;}
}

et un objet spécialisé de type DTO:

class SmallObject{
    public int Id {get;set;}
    public EnumType Type {get;set;}
    public string FieldC {get;set;}
    public string FieldN {get;set;}
}

Personnellement, je trouve très intuitif et lisible un concept de conversion explicite de BigObject en SmallObject - sachant qu'il s'agit d'une opération unidirectionnelle avec perte de données.

var small = (SmallObject) bigOne;
passSmallObjectToSomeone(small);

Il est implémenté à l'aide d'un opérateur explicite:

public static explicit operator SmallObject(BigObject big){
    return new SmallObject{
        Id = big.Id,
        FieldC = big.FieldC,
        FieldN = big.FieldN,
        EnumType = MyEnum.BigObjectSpecific
    };
}

Maintenant, je pourrais créer une classe SmallObjectFactory avec la méthode FromBigObject(BigObject big), qui ferait la même chose, l'ajouter à l'injection de dépendances et l'appeler si nécessaire ... mais cela me semble encore plus trop compliqué et inutile.

PS Je ne sais pas si cela est pertinent, mais il y aura OtherBigObject qui pourra également être converti en SmallObject, en définissant différents EnumType.

25
Gerino

Aucune des autres réponses ne me convient à mon humble avis. Dans cette question stackoverflow la réponse ayant obtenu le vote le plus élevé fait valoir que le code de mappage doit être conservé hors du domaine. Pour répondre à votre question, non - votre utilisation de l'opérateur de distribution n'est pas excellente. Je vous conseille de créer un service de cartographie qui se situe entre votre DTO et votre objet de domaine, ou vous pouvez utiliser automapper pour cela.

0

C'est ... Pas génial. J'ai travaillé avec du code qui a fait cette astuce intelligente et cela a conduit à la confusion. Après tout, vous vous attendez à pouvoir simplement affecter la variable BigObject à une variable SmallObject si les objets sont suffisamment liés pour les transtyper. Cela ne fonctionne pas cependant - vous obtenez des erreurs de compilation si vous essayez, car en ce qui concerne le système de type, elles ne sont pas liées. Il est également légèrement désagréable pour l'opérateur de moulage de fabriquer de nouveaux objets.

Je recommanderais plutôt une méthode .ToSmallObject(). Il est plus clair de ce qui se passe réellement et à peu près aussi verbeux.

81
Telastyn

Bien que je puisse voir pourquoi vous auriez besoin d'un SmallObject, j'aborderais le problème différemment. Mon approche de ce type de problème consiste à utiliser un Facade . Son seul but est d'encapsuler BigObject et de ne rendre disponible que des membres spécifiques. De cette façon, il s'agit d'une nouvelle interface sur la même instance, et non d'une copie. Bien sûr, vous pouvez également vouloir effectuer une copie, mais je vous recommande de le faire via une méthode créée à cet effet en combinaison avec la façade ( par exemple return new SmallObject(instance.Clone())).

La façade présente un certain nombre d'autres avantages, à savoir garantir que certaines sections de votre programme ne peuvent utiliser que les membres mis à disposition via votre façade, garantissant ainsi qu'elle ne peut pas utiliser ce qu'elle ne devrait pas savoir. En plus de cela, il a également l'énorme avantage que vous avez plus de flexibilité pour changer BigObject dans la maintenance future sans avoir à vous soucier trop de la façon dont il est utilisé tout au long de votre programme. Tant que vous pouvez émuler l'ancien comportement sous une forme ou une autre, vous pouvez faire fonctionner SmallObject de la même manière qu'avant sans avoir à changer votre programme partout où BigObject aurait été utilisé.

Remarque, cela signifie que BigObject ne dépend pas de SmallObject mais plutôt l'inverse (comme cela devrait être à mon humble avis).

11
Neil

Il existe une convention très stricte selon laquelle les transtypages sur les types de référence mutables préservent l'identité. Étant donné que le système n'autorise généralement pas les opérateurs de conversion définis par l'utilisateur dans les situations où un objet du type source pourrait être affecté à une référence du type de destination, il n'y a que quelques cas où les opérations de conversion définies par l'utilisateur seraient raisonnables pour une référence mutable les types.

Je suggérerais comme exigence que, étant donné x=(SomeType)foo; suivi quelque temps plus tard par y=(SomeType)foo;, les deux transtypages étant appliqués au même objet, x.Equals(y) devrait toujours et à jamais être vrai, même si l'objet en question a été modifié entre les deux lancers. Une telle situation pourrait s'appliquer si par exemple l'un avait une paire d'objets de types différents, chacun détenant une référence immuable à l'autre, et le fait de jeter l'un ou l'autre objet sur l'autre type rendrait son instance appariée. Il pourrait également s'appliquer aux types qui servent de wrappers aux objets mutables, à condition que les identités des objets en train d'être enveloppés soient immuables, et que deux wrappers du même type se déclarent égaux s'ils enveloppent les mêmes collection.

Votre exemple particulier utilise des classes mutables, mais ne préserve aucune forme d'identité; en tant que tel, je dirais que ce n'est pas une utilisation appropriée d'un opérateur de casting.

6
supercat

Ça pourrait aller.

Un problème avec votre exemple est que vous utilisez de tels noms d'exemple. Considérer:

SomeMethod(long longNum)
{
  int num = (int)longNum;
  /* ... */

Maintenant, quand vous avez une bonne idée de ce que signifie long et int, alors la conversion implicite de int en long et les transtypages explicites de long vers int sont tout à fait compréhensibles. Il est également compréhensible que 3 devient 3 et n'est qu'une autre façon de travailler avec 3. Il est compréhensible que cela échoue avec int.MaxValue + 1 dans un contexte vérifié. Même comment cela fonctionnera avec int.MaxValue + 1 dans un contexte non contrôlé pour aboutir à int.MinValue n'est pas la chose la plus difficile à grogner.

De même, lorsque vous effectuez un cast implicite vers un type de base ou explicitement vers un type dérivé, il est compréhensible pour quiconque sait comment l'héritage fonctionne, ce qui se passe et quel sera le résultat (ou comment il pourrait échouer).

Maintenant, avec BigObject et SmallObject je ne sais pas comment fonctionne cette relation. Si vos types réels sont tels que la relation de casting est évidente, le casting peut en effet être une bonne idée, bien que la plupart du temps, peut-être la grande majorité, si tel est le cas, cela devrait se refléter dans la hiérarchie des classes et un lancer normal basé sur l'héritage suffira.

1
Jon Hanna