2010-08-08 12 views
18

Quiero crear mi propia clase EMailAddress que actúa como una clase de cadena.Crear una clase de cadena personalizada

lo que me gusta hacer esto

private EMailAddress _emailAddress = "[email protected]"; 

en lugar de

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

¿Hay alguna manera de lograr lo que quiero o necesito para usar la segunda alternativa. Como la cuerda está sellada, no puedo usar eso, y el operador = no se puede sobrecargar, así que no tengo ideas de cómo solucionarlo ...

+6

Si se ve como una cadena, debe ser una cadena. No estoy seguro de que sea posible, e incluso es una buena idea. – zerkms

+0

¿La clase de cadena es defectuosa? –

+2

Nadie esperaría ver ese tipo de cosas. Solo crea tu propia clase. –

Respuesta

28

Usted puede, con una conversión 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); 
    } 
} 

conversiones implícitas sólo deben utilizarse si no se pierden datos en la conversión. Incluso entonces, le recomiendo que use esta característica con moderación, ya que puede hacer que su código sea más difícil de leer. En este ejemplo, el operador implícito devuelve null cuando se pasa una cadena null. Como se comentó Jon Hanna correctamente, no es deseable tener estos dos fragmentos de código comportan de manera diferente:

// 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

Esto tiene 'EmailAddress x = null;' se comportan de manera diferente a 'String s = null; EmailAddress x = s;' que es extraño . Es mejor que el operador implícito devuelva nulo en el parámetro nulo. –

+0

Las conversiones implícitas parecen frustrar la idea de tener una clase separada. –

+0

@ Anton, ¿por qué? ¿Crees que el hecho de que podemos lanzar implícitamente un int al doble derrota el punto de tenerlos por separado? –

0

Como has notado, no puedes heredar de la cadena, pero tú puede extenderlo con métodos de extensión.

Para que pueda hacer algunos métodos de extensión específicos de correo electrónico para manejar lo que sea que estaba pensando en poner en la clase EmailAddress.

+0

¿Cómo se manejaría una asignación con una cadena con el método de extensión? –

+0

Sin asignación. Supuse que había una razón para hacer una clase que no fuera la asignación. – Sruly

+0

Personalmente, preferiría crear mi propia clase EMailAddress, en lugar de agregar correo no deseado específico de la dirección de correo electrónico a 'System.String' mediante métodos de extensión. Sin embargo, hay una clase decente 'MailAddress' :) – Thorarin

8

Para ello, añada un implicit operator from string a su tipo personalizado.

class EMailAddress 
{ 
     // ...other members 

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

Sin embargo, recomiendo usar esto con moderación.

+0

que es realmente espectacular. Pensé que algo así existía. ¡Debería tratar de no olvidarme de eso otra vez! EDITAR: ah, no entendí tu edición. ¿Por qué recomiendas usarlo con moderación? Supongo que siempre podría terminar de leer la página msdn ... –

+0

Con moderación = esto viola el principio de menor asombro. Los programadores no esperarían esto, por lo tanto, complicarían el código y se comportarían de forma inesperada. PERO, la clase de cadena ES defectuosa, y la cadena viola POLA al ser un tipo de referencia pero que actúa como un tipo de valor. Muchas de mis clases genéricas abstractas requieren constructores sin parámetros, por lo que no puedo usar la clase String en mis genéricos. Así que creé una clase StringMutable para que se pueda usar en mis clases abstractas, donde: new() –

0

Creo ver lo que estás tratando de hacer. Pero no puede subclasificar string en .NET. Por momentos deseé poder clasificar tipos incorporados como int, pero por supuesto tampoco es posible (aunque por otros motivos técnicos).

Si bien teóricamente podrías usar un método de conversión implicit operator, como sugirieron otros aquí, hay algunos problemas con ellos, como errores extraños en el compilador de conversión de tipos. Esa solución no me ha funcionado muy bien.

lo que normalmente se ve en el BCL en escenarios similares — es decir, cuando un tipo especializado debe "sentir" como el tipo que se basa en — es un tipo personalizado tener un Value propiedad. (Dos ejemplos que se me ocurren son System.Nullable<T>, System.Lazy<T>). No es tan elegante, pero funciona.

2
  1. Un operador implícito debe emitir un valor nulo a otro valor nulo, excepto cuando el tipo de conversión a no es anulable, en cuyo caso debe error en nulo.
  2. Por favor, si está escribiendo algo que contiene un Uri, no obligue a las personas que lo usan que tienen un Uri a que hagan el trabajo de obtener la cadena por sí mismos. Las direcciones de correo electrónico se ajustan naturalmente a mailto: uris, por lo que aunque esto no es todo un ejemplo de esto, está cerca.

Ejemplo:

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

Añadir sentido de validación de direcciones de correo electrónico a la clase tiene sentido . Sin embargo, las conversiones * implícitas que pueden fallar son un poco olor a código, imo. – Thorarin

+0

Además, estoy un poco decepcionado de que no haya implementado la validación completa de RFC822 (o RFC5322 que lo reemplaza); – Thorarin

+0

La validación completa significaría agregar una Regex realmente larga en el ejemplo, y a menudo no es lo que es realmente querido de todos modos. En cuanto a si es un mal olor de código, los olores de código son relativos. Me inclinaría a no permitir un implícito (y si estuviera creando esta clase yo mismo, probablemente me apoye lo suficiente como para no hacerlo), pero no lo vería como un claro no-no. –

1

creo que esta cuestión es un caso específico de una pregunta más general sobre la creación de la seguridad de tipos a través de una aplicación de C#. Mi ejemplo aquí es con 2 tipos de datos: precios y pesos. Tienen diferentes unidades de medida, por lo que nunca se debe tratar de asignar un precio a un peso o viceversa. Ambos debajo de las cubiertas son valores realmente decimales. (Estoy ignorando el hecho de que podría haber conversiones como libras por kilogramo, etc.). Esta misma idea podría aplicarse a cadenas con tipos específicos como EmailAddress y UserLastName.

Con un poco de código de la placa de la caldera uno puede hacer una conversión explícita o conversiones implícitas hacia adelante y hacia atrás entre los tipos específicos: Precio y Peso, y el tipo subyacente Decimal.

public class Weight 
{ 
    private readonly Decimal _value; 

    public Weight(Decimal value) 
    { 
     _value = value; 
    } 

    public static explicit operator Weight(Decimal value) 
    { 
     return new Weight(value); 
    } 

    public static explicit operator Decimal(Weight value) 
    { 
     return value._value; 
    } 
}; 


public class Price { 
    private readonly Decimal _value; 

    public Price(Decimal value) { 
     _value = value; 
    } 

    public static explicit operator Price(Decimal value) { 
     return new Price(value); 
    } 

    public static explicit operator Decimal(Price value) 
    { 
     return value._value; 
    } 
}; 

Con las anulaciones de operador "explícitas", uno obtiene un conjunto más restrictivo de cosas que uno puede hacer con estas clases. Tiene que ser un caso manual cada vez que cambie de un tipo a otro. Por ejemplo:

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 
    } 

Simplemente cambiar los "explícitas" operadores a los operadores "implícitas" mediante la sustitución de las palabras "explícito" con "implícita" en el código, se puede convertir de ida y vuelta a la clase decimal subyacente sin ningún trabajo extra. Esto hace que el Precio y el Peso se comporten más como un Decimal, pero aún no puede cambiar un Precio por un Peso. Este suele ser el nivel de seguridad que estoy buscando.

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 
    }  

Al hacerlo por cadena en lugar de decimal. Me gusta la idea que la respuesta de Thorarin de comprobar nulo y pasar nulo en la conversión. p.ej.

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); 
} 

para conseguir estas clases de trabajo como claves para las colecciones de diccionarios, también tendrá que aplicar iguales, GetHashCode, == operador, y el operador! =

Para hacer todo esto más fácil, hice una clase ValueType que puedo extender, la clase ValueType llama al tipo de base para todo menos para los operadores de conversión.

Cuestiones relacionadas