Archivo de la etiqueta: Herramientas

Una colección para tus nombres-Valor


Siguiendo con la biblioteca de útiles, veamos ahora de tener una clase que nos permita almacenar y manipular varias instancias de la clase NameValue de la publicación pasada.

Personalizando una lista

Esto en realidad, puede ser tan sencillo como crear una clase que herede de la genérica List. Sin embargo, para hacerla debemos definir de que clase es esa lista y, como vimos anteriormente, estamos definiendo diferentes clases, de acuerdo al tipo de dato a almacenar.

Para ello, viene  en nuestra ayuda la interfaz INameValue.

Public Class NameValueList
    Inherits List(Of INameValue)

Y ya la tenemos. Sonrisa

Sin embargo, podríamos agregar algunas funcionalidades como el agregado de un elemento, dependiendo del tipo de dato.

Esto permitiría tener un método genérico que “traduzca” tipos de datos, por ejemplo, cuando necesitamos transformar desde otros entornos (como datos venidos desde bases de datos)

Automatizando el agregado de elementos

Definamos pues, un método que cree una instancia de la clase NameValue, y le asigne el valor recibido:

Private Function CreateNameValue(
                    name As String,
                    value As Object,
                    type As Type) As INameValue

Como vemos, el valor se recibe como de tipo objeto, que admitirá cualquier valor, y en parámetro independiente, el tipo deseado.

Lo que retorna la función es la interfaz genérica INameValue.

Internamente, la función define una variable para crear el valor de retorno, y obtenemos, como cadena de caracteres, el nombre del tipo a utilizar.

        Dim retvalue As INameValue
        Dim sName As String = type.Name

Luego, seleccionamos basados en dicho nombre, para crear la instancia a retornar, utilizando la sentencia Select Case

Select Case sName
    Case "BigInt"
        retvalue =
            New NameValue(Of Int64)(name)
    Case "Binary"
        retvalue =
            New NameValue(Of Byte())(name)
    Case "Bit"
        retvalue =
            New NameValue(Of Boolean)(name)

(la lista es bastante más larga).

Una ventaja de la sentencia Select es que nos permite ejecutar una acción, al seleccionar uno de varios valores en la misma sentencia. Así, por ejemplo, para los valores de cadenas de caracteres, podemos agruparlos en un solo case. Además, en el ejemplo, vemos que no solo usamos los tipos de datos propios de .Net, sino también, otros como VarChar, NVarchar, Text, que son propios de bases de datos:

Case "NChar",
        "NText",
        "NVarChar",
        "Text",
        "VarChar",
        "Xml",
        "String"
    retvalue =
        New NameValue(Of String)(name)

Ante la ocurrencia no cubierta, mostrar el error.

Aún cuando queramos ser muy detallistas, es factible que no contemplemos todos los tipos posibles. Por ello, si nos encontramos ante esa situación, es importante informarnos de ello con una excepción específica. Cuando ninguna de las opciones de selección ocurre, (el caso diferente), procedemos a ello:

Case Else
   Debug.WriteLine(
      String.Format(
         "Case ""{0}",type.ToString))
   Debug.WriteLine(
      String.Format(
         "retvalue = New NameValue(Of {0})(name)",
         type.ToString))
   Throw New NotImplementedException(
      String.Format(
         "Missing type={0}",
         type.ToString)
      )

Pero también existen los tipos que admiten valores nulos.

Como es posible recibir de esos tipos de datos, debiéramos contemplar también esa posibilidad.

para ello, detectamos si el tipo admite nulos (es Nullable), y utilizamos un select similar, para dichos valores, dejando el ya definido, para los que no lo admiten, encapsulando esta decisión, obviamente, con una sentencia If.

        Dim isNullable As Boolean =
            type.FullName.StartsWith("System.Nullable")
        If isNullable Then
            'Get the base type Name
            Dim splitter = Split(type.FullName, "[")
            splitter = splitter(2).Split(",")
            sName = splitter(0).Replace("System.", "")
        End If
        If isNullable Then

Exponiendo la creación de nuevos elementos.

Todo este procedimiento lo hemos incluido en un miembro privado, solo visible dentro de la propia clase, para crear el nuevo elemento. Definamos pues un método visible externamente que además de crearlo, lo agregue a la lista.

    Public Sub AddElement(
                name As String,
                value As Object,
                type As Type)
        Me.Add(
            CreateNameValue(
                name,
                value,
                type)
            )
    End Sub

Obteniendo un valor por su nombre.

La clase base List solo es capaz de retornar un elemento específico por su índice pero no por un literal (lo cual, por cierto, si puede hacerlo un Dictionary).

Pero elegimos List para que fuese más liviano y además, fácilmente serializable.

Además, perfectamente podemos implementar el método que nos retorne el valor por el nombre, con el siguiente código.

    Public Function GetByName(
                        name As String) As INameValue
        Return (
            From el
            In Me
            Where
                el.Name.ToUpper =
                name.ToUpper
                ).FirstOrDefault
    End Function

Eehhh si, estoy usando LinQ. (ya voy a publicar algo específico de esto enseguida) Sonrisa

Y así quedaría el código completito

Public Class NameValueList
    Inherits List(Of INameValue)
    Public Sub AddElement(
                name As String,
                value As Object,
                type As Type)
        Me.Add(
            CreateNameValue(
                name,
                value,
                type)
            )
    End Sub
    Private Function CreateNameValue(
                        name As String,
                        value As Object,
                        type As Type) As INameValue
        Dim retvalue As INameValue
        Dim sName As String = type.Name
        Dim isNullable As Boolean =
            type.FullName.StartsWith("System.Nullable")
        If isNullable Then
            'Get the base type Name
            Dim splitter = Split(type.FullName, "[")
            splitter = splitter(2).Split(",")
            sName = splitter(0).Replace("System.", "")
        End If
        If isNullable Then
            Select Case sName
                Case "BigInt"
                    retvalue = New NameValue(Of Int64?)(name)
                Case "Bit"
                    retvalue = New NameValue(Of Boolean?)(name)
                Case "Boolean"
                    retvalue = New NameValue(Of Boolean?)(name)
                Case "Char"
                    retvalue = New NameValue(Of Char?)(name)
                Case "DateTime"
                    retvalue = New NameValue(Of DateTime?)(name)
                Case "Decimal"
                    retvalue = New NameValue(Of Decimal?)(name)
                Case "Float"
                    retvalue = New NameValue(Of Decimal?)(name)
                Case "Int"
                    retvalue = New NameValue(Of Integer?)(name)
                Case "Money"
                    retvalue = New NameValue(Of Decimal?)(name)
                Case "Real"
                    retvalue = New NameValue(Of Double?)(name)
                Case "UniqueIdentifier"
                    retvalue = New NameValue(Of Guid?)(name)
                Case "SmallDateTime"
                    retvalue = New NameValue(Of DateTime?)(name)
                Case "SmallInt"
                    retvalue = New NameValue(Of Int16?)(name)
                Case "SmallMoney"
                    retvalue = New NameValue(Of Decimal?)(name)
                Case "TinyInt"
                    retvalue = New NameValue(Of Int16?)(name)
                Case "Date", "System.DateTime"
                    retvalue = New NameValue(Of Date?)(name)
                Case "Time"
                    retvalue = New NameValue(Of DateTime?)(name)
                Case "DateTime2"
                    retvalue = New NameValue(Of DateTime?)(name)
                Case "DateTimeOffset"
                    retvalue = New NameValue(Of TimeSpan?)(name)
                Case "Int32"
                    retvalue = New NameValue(Of System.Int32?)(name)
                Case "Int16"
                    retvalue = New NameValue(Of System.Int16?)(name)
                Case "Int64"
                    retvalue = New NameValue(Of System.Int64?)(name)
                Case "Double"
                    retvalue = New NameValue(Of System.Double?)(name)
                Case Else
                    Debug.WriteLine(String.Format("Case ""{0}", type.ToString))
                    Debug.WriteLine(String.Format("retvalue = New NameValue(Of {0})(name)", type.ToString))
                    Throw New NotImplementedException(String.Format("Missing type={0}", type.ToString))
            End Select

        Else
            Select Case sName
                Case "BigInt"
                    retvalue =
                        New NameValue(Of Int64)(name)
                Case "Binary"
                    retvalue =
                        New NameValue(Of Byte())(name)
                Case "Bit"
                    retvalue =
                        New NameValue(Of Boolean)(name)
                Case "Boolean"
                    retvalue = New NameValue(Of Boolean)(name)
                Case "Char"
                    retvalue = New NameValue(Of Char)(name)
                Case "DateTime"
                    retvalue = New NameValue(Of DateTime)(name)
                Case "Decimal"
                    retvalue = New NameValue(Of Decimal)(name)
                Case "Float"
                    retvalue = New NameValue(Of Decimal)(name)
                Case "Image"
                    retvalue = New NameValue(Of Byte())(name)
                Case "Int"
                    retvalue = New NameValue(Of Integer)(name)
                Case "Money"
                    retvalue = New NameValue(Of Decimal)(name)
                Case "NChar",
                        "NText",
                        "NVarChar",
                        "Text",
                        "VarChar",
                        "Xml",
                        "String"
                    retvalue =
                        New NameValue(Of String)(name)
                Case "Real"
                    retvalue = New NameValue(Of Double)(name)
                Case "UniqueIdentifier"
                    retvalue = New NameValue(Of Guid)(name)
                Case "SmallDateTime"
                    retvalue = New NameValue(Of DateTime)(name)
                Case "SmallInt"
                    retvalue = New NameValue(Of Int16)(name)
                Case "SmallMoney"
                    retvalue = New NameValue(Of Decimal)(name)
                Case "Timestamp"
                    retvalue = New NameValue(Of Byte())(name)
                Case "TinyInt"
                    retvalue = New NameValue(Of Int16)(name)
                Case "VarBinary"
                    retvalue = New NameValue(Of Byte())(name)
                Case "Variant"
                    retvalue = New NameValue(Of Object)(name)
                Case "Udt"
                    retvalue = New NameValue(Of Object)(name)
                Case "Structured"
                    retvalue = New NameValue(Of Object)(name)
                Case "Date", "System.DateTime"
                    retvalue = New NameValue(Of Date)(name)
                Case "Time"
                    retvalue = New NameValue(Of DateTime)(name)
                Case "DateTime2"
                    retvalue = New NameValue(Of DateTime)(name)
                Case "DateTimeOffset", "TimeSpan"
                    retvalue = New NameValue(Of TimeSpan)(name)
                Case "Int32"
                    retvalue = New NameValue(Of System.Int32)(name)
                Case "Int16"
                    retvalue = New NameValue(Of System.Int16)(name)
                Case "Int64"
                    retvalue = New NameValue(Of System.Int64)(name)
                Case "Byte[]"
                    retvalue = New NameValue(Of System.Byte())(name)
                Case "Double"
                    retvalue = New NameValue(Of System.Double)(name)
                Case Else
                    Debug.WriteLine(
                        String.Format(
                            "Case ""{0}",
                            type.ToString))
                    Debug.WriteLine(
                        String.Format(
                            "retvalue = New NameValue(Of {0})(name)",
                            type.ToString))
                    Throw New NotImplementedException(
                        String.Format(
                            "Missing type={0}",
                            type.ToString)
                        )
            End Select
        End If
        retvalue.RawValue = value
        Return retvalue
    End Function
    Public Function GetByName(
                        name As String) As INameValue
        Return (
            From el
            In Me
            Where
                el.Name.ToUpper =
                name.ToUpper
                ).FirstOrDefault
    End Function
End Class

Generalizando el código


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