Donde empezamos a crear bibliotecas de herramientas útiles.
Finalmente, luego del parate de los últimos tiempos, volvemos al ruedo con cuestiones ya directamente prácticas.
Comencemos por definir algunas herramientas útiles a nuestros quehaceres diarios.
Para ello, crearemos un proyecto de elementos comunes (de nombre Common, por ejemplo), para definir allí elementos que serán útiles en muchos otros proyectos.
En los ejemplos, utilizaré mis propios proyectos como ejemplo. Por ello, todos ellos tienen un espacio de nombres común: DS 🙂
Entonces, comencemos creando el proyecto [ComoQuierasLlamarlo].Common.
O sea, te creas n Nuevo Proyecto, eliges el leguaje de programación de tu elección (yo, prefiero VB, pero da igual).
Te recomiendo que mantengas en una misma solución, todas tus bibliotecas de utilidades, para poder vincularlas desde otras soluciones y aislar el código.
Además, si utilizas Azure Team Foundation, o cualquier otro protector de código, con Git o similares, mantenerlo en una solución protegida te ayudará a nunca perder tu código.
Clases para intercambiar valores.
Muchas veces, es necesario intercambiar valores entre funciones, bibliotecas, elementos externos, etc. y queremos mantener aisladas las referencias a bibliotecas específicas (por ejemplo, a bibliotecas de bases de datos, de repositorios, etc.).
También sucede que algunas bibliotecas, (de nuevo, bases de datos, por ejemplo), no siempre utilizan los mismos tipos de datos que otras. Entonces, se podría necesitar pasar valores como tipo “Object”, o sea, de modo no tipificado.
Por otra parte ya vimos que siempre que sea factible, es mejor utilizar los tipos de datos específicos.
Y necesitaremos conocer el nombre de cada uno de los valores que queremos intercambiar.
Entonces necesitaríamos
- Una clase que exponga un valor con su tipo especifico
- Que posea el nombre del valor en cuestión (o sea, nombre del argumento o propiedad)
- Una forma genérica de acceder al valor, como si fuera objeto.
Si además quisiéramos contenerlas en una lista, colección o diccionario, si las hiciésemos específicas, sería muy complejo definir dicha colección, dadas las diferencias de tipos.
Por ello, recurriremos a una interfaz de nuestros objetos “Valor-Nombre”:
Public Interface INameValue Property Name As String Property RawValue As Object End Interface
Creando clases genéricas
Estando así las cosas, y dadas nuestras necesidades, es cuando surge la posibilidad de utilizar generalizaciones (Generics) para poder definir nuestra clase de nombre-valor, acorde a cada tipo de dato.
Una clase definida como genérica, permite definir la misma para que en el momento de crear una instancia, recién allí se defina el tipo de dato en cuestión.
Se debe declarar la clase, indicando que será de un “tipo no definido” utilizando la sintaxis (Of T). (<T> en C#)
Entonces, en cada método o función que se requiera especificar el tipo que se asignará cuando se cree la instancia, se utiliza la letra T (puede ser cualquier letra, sencillamente se usa la T de Type = Tipo)
Por ejemplo, nuestra clase expondrá el valor con el tipo específico, con la propiedad Value; entonces, la declaratoria sería:
Public Property Value As T
- La propiedad Nombre
- La propiedad RawValue que, al recibir un valor, debe convertirlo al tipo de datos correcto.
#Region "INameValue" Implements INameValue Public Property Name As String Implements INameValue.Name Public Property Value As T Public Property RawValue As Object Implements INameValue.RawValue Get Return Me.Value End Get Set(value As Object) If Not value Is Nothing AndAlso Not TypeOf value Is DBNull OrElse TypeOf Me.Value Is Nullable Then Me.Value = CType(value, T) End If End Set End Property #End Region
Además, al ser tipos no específicos, cabe que tengamos que manipular los operadores. Como ejemplo, vayan estos dos:
Public Shared Operator =(first As NameValue(Of T), second As NameValue(Of T)) As Boolean Return first.RawValue.ToString = second.RawValue.ToString End Operator Public Shared Operator <>(first As NameValue(Of T), second As NameValue(Of T)) As Boolean Return first.RawValue.ToString <> second.RawValue.ToString End Operator
Finalmente, agreguemos un constructor para poder definir nuevas instancias pasando el nombre.
Sub New(name As String) Me.Name = name End Sub
El mismo código, en lenguaje C#.
public class NameValue<T> : INameValue { public string Name { get; set; } public object RawValue { get { return this.Value; } set { if (value != null && value != DBNull.Value || Nullable.GetUnderlyingType(this.Value.GetType()) != null) { this.Value = (T)value; } } } public T Value; public static bool operator ==(NameValue<T> first, NameValue<T> second) { return first.RawValue.ToString() == second.RawValue.ToString(); } public static bool operator !=(NameValue<T> first, NameValue<T> second) { return first.RawValue.ToString() != second.RawValue.ToString(); } public NameValue(string name) { this.Name = name; } }