2010-08-08 8 views
18

de chaîne personnalisée Je veux créer ma propre classe EMAILADDRESS qui agit comme une classe de chaîne.Créer une classe

J'aime le faire

private EMailAddress _emailAddress = "[email protected]"; 

au lieu de

private EMailAddress _emailAddress = new EMailAddress("[email protected]"); 

Est-il possible d'accomplir ce que je veux, ou dois-je utiliser la seconde alternative. Puisque chaîne Je ne peux pas utiliser est scellée que, et l'opérateur = ne peut pas être surchargé, donc je suis d'idées comment résoudre ce problème ....

+6

Si cela ressemble à une chaîne - ce devrait être une chaîne. Je ne suis pas sûr que ce soit possible et même c'est une bonne idée. – zerkms

+0

La classe de chaînes est-elle défectueuse? –

+2

Personne ne s'attendrait à voir ce genre de chose. Créez simplement votre propre classe. –

Répondre

28

Vous pouvez, avec une conversion implicit:

public class EMailAddress 
{ 
    private string _address; 

    public EMailAddress(string address) 
    { 
     _address = address; 
    } 

    public static implicit operator EMailAddress(string address) 
    { 
     // While not technically a requirement; see below why this is done. 
     if (address == null) 
      return null; 

     return new EMailAddress(address); 
    } 
} 

Les conversions implicites ne doivent être utilisées si aucune perte de données lors de la conversion. Même alors, je vous recommande d'utiliser cette fonctionnalité avec parcimonie, car cela peut rendre votre code plus difficile à lire.

Dans cet exemple, l'opérateur implicite renvoie null lorsqu'une chaîne null est transmise. Comme correctement Jon Hanna a commenté, il est souhaitable d'avoir ces deux extraits de code se comportent différemment:

// Will assign null to the reference 
EMailAddress x = null; 

// Would create an EMailAddress object containing a null string 
string s = null; 
EMailAddress y = s; 
+3

Ceci a 'EmailAddress x = null;' se comporte différemment de 'String s = null; EmailAddress x = s;' ce qui est étrange . Mieux vaut que l'opérateur implicite renvoie null sur le paramètre null. –

+0

Les conversions implicites semblent aller à l'encontre de l'idée d'avoir une classe séparée. –

+0

@Anton, pourquoi? Pensez-vous que le fait que nous puissions implicitement convertir un int en double défait le point de les avoir séparément? –

0

Comme vous l'avez dit vous ne pouvez pas hériter de chaîne mais vous peut l'étendre avec des méthodes d'extension.

Vous pouvez donc faire des méthodes d'extension spécifiques de messagerie pour gérer quoi que ce soit que vous envisagez de mettre dans la classe EmailAddress.

+0

Comment gérer une affectation avec une chaîne avec la méthode d'extension? –

+0

Pas d'affectation. J'ai supposé qu'il y avait une raison pour faire un cours autre que la simple affectation. – Sruly

+0

Personnellement, je préférerais créer ma propre classe EMailAddress, plutôt que d'ajouter des indésirables spécifiques à l'adresse e-mail à 'System.String' à l'aide de méthodes d'extension. Il y a cependant une classe de 'MailAddress' assez décente :) – Thorarin

8

vous faites cela en ajoutant un implicit operator from string à votre type personnalisé.

class EMailAddress 
{ 
     // ...other members 

     public static implicit operator EMailAddress (string address) 
     { 
      return new EMailAddress(address); 
     } 
} 

Cependant, je recommande d'utiliser ceci avec parcimonie.

+0

c'est vraiment spiffy. Je pensais que quelque chose comme ça existait. Je devrais essayer de ne plus jamais l'oublier! EDIT: ah, je n'ai pas attrapé votre édition. pourquoi recommandez-vous de l'utiliser avec parcimonie? Je suppose que je pourrais toujours finir de lire msdn page ... –

+0

Sparingly = ceci viole le principe de moindre étonnement. Les programmeurs ne s'y attendraient pas, rendant le code compliqué et se comportant de manière inattendue. MAIS, la classe de chaînes est défectueuse, et la chaîne viole POLA en étant un type de référence mais agissant comme un type de valeur. Beaucoup de ma classe générique abstraite nécessite des constructeurs sans paramètres, donc je ne peux pas utiliser la classe String dans mes génériques. J'ai donc créé une classe StringMutable pour qu'elle puisse être utilisée dans mes classes abstraites, où: new() –

0

Je pense que je vois ce que vous essayez de faire. Mais vous ne pouvez pas sous-classer string dans .NET. Parfois j'ai souhaité pouvoir sous-classer des types incorporés tels que int, mais bien sûr ce n'est pas possible non plus (bien que pour d'autres raisons techniques).

Alors que vous pourriez théoriquement utiliser une méthode de conversion implicit operator, comme suggéré par d'autres ici, il y a quelques problèmes avec ceux-ci, tels que les erreurs bizarre de compilateur de type. Cette solution n'a pas très bien fonctionné pour moi.

Ce que vous voyez habituellement dans la BCL dans des scénarios similaires — à savoir quand un type spécialisé devrait « se sentir » comme le type il est basé sur — est un type personnalisé ayant une Value propriété. (Deux exemples qui viennent à l'esprit sont System.Nullable<T>, System.Lazy<T>.) Ce n'est pas aussi élégant, mais cela fonctionne. Un opérateur implicite doit convertir une valeur null en une autre valeur nulle, sauf si le type cast n'est pas Nullable, auquel cas il doit être erroné sur null.

2
  1. S'il vous plaît, si vous écrivez quelque chose qui contient un Uri, ne forcez pas les gens qui l'utilisent à avoir un Uri à faire le travail pour obtenir la chaîne pour eux-mêmes. Les adresses e-mail s'adaptent naturellement à mailto: uris, alors que ce n'est pas tout à fait un exemple, c'est proche.

Exemple:

public class EmailAddress 
{ 
    private string _value; 
    private static bool IsValidAddress(string address) 
    { 
     //whether to match RFC822 production or have something simpler, 
     //but excluding valid but unrealistic addresses, is an impl. choice 
     //out of scope. 
     return true; 
    } 
    public EMailAddress(string value) 
    { 
     if(value == null) 
      throw new ArgumentNullException(); 
     if(!IsValidAddress(value)) 
      throw new ArgumentException(); 
     _value = value; 
    } 
    public EmailAddress(Uri uri) 
    { 
     if(value == null) 
      throw new ArgumentNullException(); 
     if(!uri.Scheme != "mailto") 
      throw new ArgumentException(); 
     string extracted = uri.UserInfo + "@" + uri.Host; 
     if(!IsValidAddress(extracted)) 
      throw new ArgumentException(); 
     _value = extracted; 
    } 
    public override string ToString() 
    { 
     return _value; 
    } 
    public static implicit operator EMailAddress(string value) 
    { 
     return value == null ? null : new EMailAddress(value); 
    } 
    public static implicit operator EMailAddress(Uri uri) 
    { 
     return value == null ? null : new EMailAddress(uri); 
    } 
} 
+1

L'ajout de la validation de l'adresse e-mail à la classe est logique . Cependant, les conversions * implicites * qui peuvent échouer sont un peu une odeur de code, imo. – Thorarin

+0

Aussi, je suis un peu déçu que vous n'ayez pas implémenté la RFC822 complète (ou la RFC5322 qui la remplace); – Thorarin

+0

Bien la validation complète signifierait simplement ajouter une très longue expression dans l'exemple, et souvent ce n'est pas ce que est vraiment voulu de toute façon. Quant à savoir si c'est une mauvaise odeur de code, les odeurs de code sont relatives. Cela m'appuierait de ne pas permettre un implicite (et si je devais créer moi-même ce genre de cours, je me pencherai probablement assez sur le fait que je ne le ferais pas), mais je ne le considérerais pas comme un non-non catégorique. –

1

Je pense que cette question est un cas particulier d'une question plus générale sur la création de sécurité de type à travers une application C#. Mon exemple ici est avec 2 types de données: les prix et les poids. Ils ont différentes unités de mesure, donc on ne devrait jamais essayer d'attribuer un prix à un poids ou vice versa. Les deux sous les couvertures sont vraiment des valeurs décimales. (J'ignore le fait qu'il pourrait y avoir des conversions comme des livres en kilogrammes etc.) Cette même idée pourrait être appliquée aux chaînes avec des types spécifiques comme EmailAddress et UserLastName. Avec un peu de code de plaque de chaudière, on peut effectuer une conversion explicite ou des conversions implicites entre les types spécifiques: Prix et Poids, et le type sous-jacent Décimal. Avec les remplacements d'opérateurs "explicites", on obtient un ensemble plus restrictif de choses que l'on peut faire avec ces classes. Vous devez être manuellement cas chaque fois que vous passez d'un type à l'autre. Par exemple:

public void NeedsPrice(Price aPrice) 
    { 
    } 

    public void NeedsWeight(Weight aWeight) 
    { 
    } 

    public void NeedsDecimal(Decimal aDecimal) 
    { 
    } 
    public void ExplicitTest() 
    { 

     Price aPrice = (Price)1.23m; 
     Decimal aDecimal = 3.4m; 
     Weight aWeight = (Weight)132.0m; 

     // ok 
     aPrice = (Price)aDecimal; 
     aDecimal = (Decimal)aPrice; 

     // Errors need explicit case 
     aPrice = aDecimal; 
     aDecimal = aPrice; 

     //ok 
     aWeight = (Weight)aDecimal; 
     aDecimal = (Decimal) aWeight; 

     // Errors need explicit cast 
     aWeight = aDecimal; 
     aDecimal = aWeight; 

     // Errors (no such conversion exists) 
     aPrice = (Price)aWeight; 
     aWeight = (Weight)aPrice; 

     // Ok, but why would you ever do this. 
     aPrice = (Price)(Decimal)aWeight; 
     aWeight = (Weight)(Decimal)aPrice; 

     NeedsPrice(aPrice); //ok 
     NeedsDecimal(aPrice); //error 
     NeedsWeight(aPrice); //error 

     NeedsPrice(aDecimal); //error 
     NeedsDecimal(aDecimal); //ok 
     NeedsWeight(aDecimal); //error 

     NeedsPrice(aWeight); //error 
     NeedsDecimal(aWeight); //error 
     NeedsWeight(aWeight); //ok 
    } 

changeant seulement les opérateurs « explicites » les opérateurs à « implicites » en remplaçant les mots « explicite » par « implicite » dans le code, on peut convertir dans les deux sens à la classe décimale sous-jacente sans travail supplémentaire. Cela fait que le prix et le poids se comportent plus comme une décimale, mais vous ne pouvez toujours pas modifier un prix par rapport à un poids. C'est généralement le niveau de sécurité que je recherche. Lorsque vous effectuez cette opération pour Chaîne au lieu de Décimal,

public void ImplicitTest() 
    { 
     Price aPrice = 1.23m; 
     Decimal aDecimal = 3.4m; 
     Weight aWeight = 132.0m; 

     // ok implicit cast 
     aPrice = aDecimal; 
     aDecimal = aPrice; 

     // ok implicit cast 
     aWeight = aDecimal; 
     aDecimal = aWeight; 

     // Errors 
     aPrice = aWeight; 
     aWeight = aPrice; 


     NeedsPrice(aPrice); //ok 
     NeedsDecimal(aPrice); //ok 
     NeedsWeight(aPrice); //error 

     NeedsPrice(aDecimal); //ok 
     NeedsDecimal(aDecimal); //ok 
     NeedsWeight(aDecimal); //ok 

     NeedsPrice(aWeight); //error 
     NeedsDecimal(aWeight); //ok 
     NeedsWeight(aWeight); //ok 
    }  

J'aime l'idée de la réponse de Thorarin à propos de la vérification de null et du retour de null dans la conversion. par exemple.

public static implicit operator EMailAddress(string address) 
{ 
    // Make 
    //  EmailAddress myvar=null 
    // and 
    //  string aNullString = null; 
    //  EmailAddress myvar = aNullString; 
    // give the same result. 
    if (address == null) 
     return null; 

    return new EMailAddress(address); 
} 

Pour obtenir ces classes de travail en tant que clés de collections dictionnaire, vous aurez également besoin d'implémenter equals, GetHashCode, == opérateur, et l'opérateur! =

Pour rendre tout cela plus facile, je fis classe ValueType que je peux étendre, La classe ValueType appelle le type de base pour tout sauf les opérateurs de conversion.

Questions connexes