2009-12-17 19 views
17

Quizás estoy demostrando mi ignorancia de alguna característica muy utilizada de C# o .NET framework, pero me gustaría saber si existe una forma compatible nativamente para crear un alias de tipo como EmailAddress que alias string pero tal que ¿Puedo extenderlo con mis propios métodos como bool Validate()?¿Hay alguna manera de definir los alias fuertemente tipados de C# de los tipos primitivos existentes como `string` o` int`?

Conozco los alias de using x = Some.Type; pero estos no son globales ni proporcionan seguridad de tipo, es decir, se puede cambiar un string ordinario por el alias en el archivo actual. Me gustaría que mi EmailAddress sea de su propio tipo, independiente y no intercambiable con el tipo string que sombrea.

Mi solución actual es generar clases public sealed partial EmailAddress : IEquatable<EmailAddress>, IXmlSerializable con una plantilla T4 generando los operadores de conversión de cadenas implícitas y otras cosas similares. Esto está bien para mí por ahora y me da mucha flexibilidad, pero en el fondo de mi mente parece tonto que tenga que generar cantidades tan grandes de código repetitivo para hacer algo tan simple como crear un alias de tipo fuerte.

Quizás esto no sea posible más que con la generación de código, pero tengo curiosidad por saber si otros han intentado algo similar con sus diseños y cuáles han sido sus experiencias. Si nada más, quizás esto podría servir como un buen caso de uso para dicha función de alias en una hipotética versión futura de C#. ¡Gracias!

EDITAR: El valor real que deseo de esto es poder obtener seguridad de tipo con tipos primitivos que representan diferentes tipos/formatos de datos. Por ejemplo, un EmailAddress y un SocialSecurityNumber y un PhoneNumber, todos los cuales usan string como su tipo subyacente pero que no son tipos intercambiables en sí mismos. Creo que esto te da un código mucho más legible y autodocumentado, por no mencionar los beneficios adicionales de más posibilidades de sobrecarga de métodos que son menos ambiguas.

+0

¿Por qué votar esto y no dejar un comentario? Vamos gente. – JMD

+1

Parece que tienes al menos un C# knoledgde razonable así que mi comentario puede parecer estúpido, pero lo que quieres se llama "jerarquía de tipos" y los tipos que codificaron la clase String querían evitar que uses esta "característica OO", así que hicieron Clase de cadena sellada, es por eso que no podrás hacer lo que quieras. El mejor enfoque es este en el que estás ahora: crea tu propio tipo y una conversión implícita a String. –

+0

@Andre: ¿por qué no considerar publicar tu comentario como respuesta? –

Respuesta

4

Si observa el .NET Framework System. Uri es el ejemplo más cercano que es similar a una dirección de correo electrónico. En .NET, el patrón es envolver algo en una clase y agregar restricciones de esa manera.

Agregar una tipificación fuerte que agrega restricciones adicionales a los tipos simples es una característica interesante del lenguaje que creo que tiene algún lenguaje funcional. No puedo recordar el nombre del idioma que le permitiría agregar unidades dimensionales, como pies, a sus valores y hacer un análisis dimensional de sus ecuaciones para asegurar que las unidades coincidan.

+0

Este parece ser el mejor camino a seguir. No soy muy versado en los lenguajes funcionales, pero sí sé que tienen algunos ingeniosos sistemas tipo que te permiten componer mucho mejor que uno orientado a objetos. –

+1

Creo que el lenguaje Ada tiene la característica que describes, o una similar. – AakashM

+0

Go y Haskell también tienen tales limitaciones (estoy seguro de que Go, Haskell no lo sé). – Crisfole

2

¿La clase System.Net.Mail.MailAddress se ajusta a sus necesidades, o al menos a "ayuda"?

EDITAR: No es explícitamente estable o ISerializable, pero podría fácilmente agregarlos en su propio contenedor.

+1

No, tenía la intención de pedir una solución general, no el caso específico de una dirección de correo electrónico. También estoy haciendo cosas como generar envoltorios 'int' para crear identificadores fuertemente tipados para propósitos de auto-documentación y flexibilidad en las definiciones de sobrecarga de métodos. Por ejemplo, tengo 'Staff GetStaff (ID de StaffID)' y 'Staff GetStaff (TeacherID id)' ambos definidos en la misma interfaz. Ambos métodos realizan diferentes consultas contra la fuente de datos subyacente dependiendo de qué tipo de identificador se proporciona. El profesor es-un Staff y un TeacherID de valor 1 no representa la misma entidad que StaffID con valor 1. –

+0

Entendido, malinterpreté su pregunta como preguntando por este caso específico. – JMD

4

Algunos antecedentes sobre por qué string está sellado:

De http://www.code-magazine.com/Article.aspx?quickid=0501091:

Rory: Hey Jay, te importa si te pregunto a par de preguntas? Ya tengo curiosidad sobre algunas cosas. En primer lugar, y esto se mencionó en uno de los eventos de MSDN que hice esta semana, ¿por qué se sella el String ? Nota: para los programadores de VB.NET, Sellado = No heredable.

Jay: Debido a que una gran cantidad de magia trucos de cuerdas para tratar de asegurarse de podemos optimizar para cosas como comparaciones, para que sean tan rápido como nos sea posible . Por lo tanto, estamos robando bits de punteros y otras cosas para marcar las cosas.Solo al le doy un ejemplo, y no sabía esto cuando comencé, pero si una Cadena tiene un guion o un apóstrofo en ella [entonces] ordena de manera diferente que si solo tiene texto, y el algoritmo para clasificar que si usted tiene un guión o un apóstrofe si está haciendo clasificación a nivel mundial-consciente es bastante complicada , así que en realidad marcar si la cadena tiene que tipo de comportamiento en ella.

Rory: Por lo tanto, lo que está diciendo es que en el mundo de cuerdas, si lo hizo, no cadena sello que habría un montón de espacio para causando una gran cantidad de estragos si personas estaban tratando de subclase que.

Jay: Exactamente. Cambiaría el diseño completo del objeto, por lo que no podría jugar los trucos que jugamos esa velocidad de recogida.

Aquí está el artículo CodeProject que es probable que haya visto antes:

http://www.codeproject.com/KB/cs/expandSealed.aspx

Así que sí, el operador implícito es su única solución.

+0

Gracias por la información acerca de 'string' siendo sellado, pero eso no es directamente relevante. –

+1

Sé :) Es solo que personalmente trato de descubrir por qué las cosas que me cagaron son como son. –

+0

Hay ocasiones en que sería útil poder heredar casi por completo un tipo de algo como 'cadena ', incluso si el tipo derivado no tuvo acceso a ningún miembro no público de la clase subyacente, ni anular ninguno de sus miembros, ni agregue cualquier campo Si tales tipos pudieran agregar nuevas interfaces, incluso si no pudieran tener ningún miembro, eso podría ser útil para definir cosas como interfaces profundamente inmutables (por ejemplo, si uno pudiera declarar un tipo 'DeeplyImmutableInt' que se comportara como' int' pero heredó 'IAmDeeplyImmutable', y hacer lo mismo para otros tipos incorporados, uno podría entonces ... – supercat

1

Supongo que no entiendo por qué quiere tener tanto tipos fuertes como conversión de cadenas implícita al mismo tiempo. Para mí, uno excluye al otro.

Traté de resolver el mismo problema para ints (menciona int en el título, pero no en la pregunta). Descubrí que al declarar una enumeración se obtiene un entero seguro para tipos que se debe convertir explícitamente de/a int.

actualización

enumeraciones pueden no ser destinados para conjuntos abiertos, pero todavía puede ser utilizado de tal manera. Esta muestra es de un experimento de compilación para distinguir entre las columnas con un diámetro de varias tablas en una base de datos:

enum ProcID { Unassigned = 0 } 
    enum TenderID { Unassigned = 0 } 

    void Test() 
    { 
     ProcID p = 0; 
     TenderID t = 0; <-- 0 is assignable to every enum 
     p = (ProcID)3; <-- need to explicitly convert 

     if (p == t) <-- operator == cannot be applied 
      t = -1; <-- cannot implicitly convert 

     DoProc(p); 
     DoProc(t); <-- no overloaded method found 
     DoTender(t); 
    } 

    void DoProc(ProcID p) 
    { 
    } 

    void DoTender(TenderID t) 
    { 
    } 
+0

En este caso, quise decir "fuertemente tipado" como tener su propio tipo distintivo aparte de 'cadena 'y no directamente intercambiable. Disculpas por el término sobrecarga :). Las enumeraciones no ayudan mucho para el caso 'int', ya que definen su propio alcance y no están destinadas a ser utilizadas en un conjunto abierto de casos (frente a un conjunto cerrado de valores de enumeración). –

1

Creo que desee utilizar los métodos de extensión. Le permiten extender la funcionalidad de una clase sin crear un nuevo tipo derivado.

+2

Los métodos de extensión serían atractivos si pudiera crear el alias de tipo distinto y definir el método de extensión en el alias en lugar del tipo base 'cadena'. Quiero que el compilador haga una verificación de seguridad de tipo para asegurarse de que alguien no intente accidentalmente copiar una EmailAddress en un PhoneNumber o algo así/alocado. –

2

Parece que tiene al menos un C# knoledgde razonable, por lo que mi respuesta puede parecer estúpida, pero lo que quiere se llama "jerarquía de tipos" y los chicos que codificaron la clase String querían evitar el uso de esta "característica OO" así que sellaron la clase String, por eso no podrás hacer lo que quieras. El mejor enfoque es este en el que estás ahora: crea tu propio tipo y una conversión implícita a String.

0

Hice esta clase para cubrir necesidades idénticas. Éste es el "int" tipo (también tengo una para "cadena"):

public class NamedInt : IComparable<int>, IEquatable<int> 
{ 
    internal int Value { get; } 

    protected NamedInt() { } 
    protected NamedInt(int val) { Value = val; } 
    protected NamedInt(string val) { Value = Convert.ToInt32(val); } 

    public static implicit operator int (NamedInt val) { return val.Value; } 

    public static bool operator ==(NamedInt a, int b) { return a?.Value == b; } 
    public static bool operator ==(NamedInt a, NamedInt b) { return a?.Value == b?.Value; } 
    public static bool operator !=(NamedInt a, int b) { return !(a==b); } 
    public static bool operator !=(NamedInt a, NamedInt b) { return !(a==b); } 

    public bool Equals(int other) { return Equals(new NamedInt(other)); } 
    public override bool Equals(object other) { 
     if ((other.GetType() != GetType() && other.GetType() != typeof(string))) return false; 
     return Equals(new NamedInt(other.ToString())); 
    } 
    private bool Equals(NamedInt other) { 
     if (ReferenceEquals(null, other)) return false; 
     if (ReferenceEquals(this, other)) return true; 
     return Equals(Value, other.Value); 
    } 

    public int CompareTo(int other) { return Value - other; } 
    public int CompareTo(NamedInt other) { return Value - other.Value; } 

    public override int GetHashCode() { return Value.GetHashCode(); } 

    public override string ToString() { return Value.ToString(); } 
} 

Y para consumir en su caso:

public class MyStronglyTypedInt: NamedInt { 
    public MyStronglyTypedInt(int value) : base(value) { 
     // Your validation can go here 
    } 
    public static implicit operator MyStronglyTypedInt(int value) { 
     return new MyStronglyTypedInt(value); 
    } 

    public bool Validate() { 
     // Your validation can go here 
    } 
} 

Si tiene que ser capaz de serializar (Newtonsoft.Json), avísame y agregaré el código.

Cuestiones relacionadas