Bibliotecas Comunes y Herramientas


Bien, como fue comentado en la publicación anterior, vamos a comenzar a crear nuestra propia biblioteca de herramientas y elementos de soporte (a algunos les ha dado por llamarlo “Framework” ).

Aprovechando las ventajas de diseño de Visual Studio (por cierto, estoy usando VS2017), el gráfico representa el esquema base de la solución.

Este esquema lo iremos ampliando con otras bibliotecas a futuro. (Por cierto, son bibliotecas, aunque la mayoría les digan librerías… que librerías son las que venden libros ).

Para facilitar la comprensión del código, la misma solución está desarrollada en paralelo, en C# y en VB. Lo interesante es que son perfectamente intercambiables (y mezclables, puedes usar una dll de un idioma y otra de otro, y funcionarán perfectamente).

Common.

Primero lo primero. O sea, elementos comunes que podrán ser utilizados por cualesquiera de nuestras bibliotecas


En esta biblioteca incluimos toda la implementación de los valores con nombre, a saber:

  • INameValue
  • NameValue<T>
  • NameValueList

Agregamos algunos atributos que seguramente usaremos a futuro (para implementar, por ejemplo, modelos de datos)

  • DataFieldAttribute
  • DataKeyAttribute
  • DataRelationAttribute
  • DefaultValueEnum

Y una interfaz que nos permitirá luego diferenciar errores cuando sean propios del sistema (excepciones), o los que implementen esta interfaz, aquellos que deben ser informados al usuario.

  • IUserMessageError

Tools

Como segundo proyecto de nuestro entorno de trabajo, creamos la biblioteca de Herramientas, que nos será útil para muchas acciones comunes a cualquier proyecto. En ella, que irá evolucionando seguramente, definiremos aquellos métodos que nos serán útiles repetidas veces.

Lo importante es, darse cuenta a tiempo, cuándo una función que escribimos, podría ser útil en otro caso .


Tenemos en esta biblioteca, las siguientes clases:

  • Reflection: de la que hablamos hace unas publicaciones atrás, que nos permite evaluar características de nuestras propias clases.
  • Logger: como su nombre indica, esta clase permitirá escribir en un log, sea este un archivo físico o el de Aplicación del Sistema Operativo.
  • Interaction: Este es un caso particular… me encontré en más de una ocasión, que cierta funcionalidad implementada en uno de los lenguajes de programación, no tenía correspondencia en el otro. Raro, pero a veces pasa. Como ejemplo, una de las funciones útiles en VB que no existe en C#. CallByName y que en esta clase, podemos implementar. Para mantener coherencia entre distintas versiones de bibliotecas, aun cuando en VB esa función existe como nativa, la mantenemos implementada en esta biblioteca también.

Queda aquí como ejemplo, ambas versiones de esta función, para que quede claro el concepto. Por lo demás, el código está perfectamente comentado. (en inglés, para que sirva a cualquiera que lo desee utilizar, sin barreras de idioma).

En la próxima, una biblioteca que plantea algún desafío interesante, y una herramienta de Visual Studio que nos permite subsanarlo… y la publicación del conjunto completo de código.


public static object CallByName(
    object ObjectRef,
    string ProcName,
    CallType UseCallType,
    params object[] Args
    )
{
    switch (UseCallType)
    {
        case CallType.Method:
            MethodInfo m = ObjectRef.GetType().GetMethod(ProcName);
            return m.Invoke(ObjectRef, Args);
        case CallType.Get:
            PropertyInfo p = ObjectRef.GetType().GetProperty(ProcName);
            return p.GetValue(ObjectRef, Args);
        case CallType.Let:
        case CallType.Set:
            {
                PropertyInfo pL = ObjectRef.GetType().GetProperty(ProcName);
                if (Args == null)
                {
                    pL.SetValue(ObjectRef, null);

                }
                else
                {
                    pL.SetValue(ObjectRef, Args[0]);
                }
                return null;
            }
    }
    throw new ArgumentException(string.Format("Invalid CallType {0}", UseCallType));
}


Public Shared Function CallByName(
                                 ObjectRef As Object,
                                 ProcName As String,
                                 UseCallType As CallType,
                                 ParamArray Args As Object()
                                 ) As Object
    Return Microsoft.VisualBasic.CallByName(
                        ObjectRef,
                        ProcName,
                        UseCallType,
                        Args
                    )
End Function

 

Reflection


Donde nos ponemos a investigar un poco que tienen nuestros objetos.

Donde nos ponemos a investigar un poco que tienen nuestros objetos.

Motivación.

Para muchas de las cosas que venimos explicando, acerca de generalizaciones de código, métodos compartidos y demás, cuando programemos, seguramente querremos interactuar con dichas generalizaciones, pero de un modo más específico.

Por ejemplo, en la anterior publicación, hablábamos de Atributos.

¿Cómo hago para saber si una propiedad tiene determinado atributo?

¿Cómo obtengo los miembros que tienen cierto atributo?

El espacio de nombres System.Reflection.

El .Net Framework implementa este espacio de nombres específico, para poder investigar los objetos.

Con él, a partir del Type de un objeto, se puede obtener información de sus miembros, sus atributos, su jerarquía de herencia etc.

El espacio expone objetos que sirven como descriptores de las características de un tipo, como, por Ejemplo:

  • Assembly
  • MemberInfo
  • PropertyInfo

Dichos objetos se obtienen, como fue dicho, del Type de un objeto o instancia utilizando el método GetType()

Obtener el tipo

Existen distintos métodos para obtener el tipo, dependiendo si se tiene o no una instancia del mismos.

Los métodos, todos retornando un objeto Type, se describen en el siguiente cuadro

    

C#

VB

Con Instancia

variable.GetType()

variable.GetType

Sin Instancia

typeof(Nombre_De_Clase)

GetType(Nombre_de_Clase)

Funciones.

En muchos casos, utilizaremos LinQ sobre objetos para obtener información (lo cual facilita la codificación y acelera el proceso).

Comprobar Propiedades.

Comprobar si tiene un atributo


private static bool HasAttribute(PropertyInfo p, string attributeName)
{
   var attrs = p.GetCustomAttributesData();
    if (attrs.Count > 0)
    {
        var attrs2 = (from aa in attrs where aa.AttributeType.Name == attributeName select aa).ToList();
        return attrs2.Count > 0;
    }
    return false;
}


Private Shared Function HasAttribute(p As PropertyInfo, attributeFullName As StringAs Boolean
    Dim attrs = p.GetCustomAttributesData
    If attrs.Count > 0 Then
        Dim attrs2 = (From aa In attrs Where aa.AttributeType.Name = attributeFullName).ToList
        Return attrs2.Count > 0
    End If
    Return False
End Function

Como se ve, se aplica sobre un objeto PropertyInfo y filtra directamente por nombre.

Obtener los atributos de una propiedad


public static NameValueList GetAttributes(PropertyInfo property)
{
    NameValueList result = new NameValueList();
    var v = property.GetCustomAttributesData();
    foreach (var a_loopVariable in v)
    {
        var a = a_loopVariable;
        foreach (var named_loopVariable in a.NamedArguments)
        {
            var named = named_loopVariable;
            NameValue<object> nv = new NameValue<object>(named.MemberName) { RawValue = named.TypedValue.Value };
            result.Add(nv);
        }
    }
    return result;
}


Public Shared Function GetAttributes(ByVal [property] As PropertyInfoAs DSCommon.NameValueList
    Dim result As New DSCommon.NameValueList
    Dim v = [property].GetCustomAttributesData()
    For Each a In v
        For Each named In a.NamedArguments
            Dim nv As New DSCommon.NameValue(Of Object)(named.MemberName) With {.RawValue = named.TypedValue.Value}
            result.Add(nv)
        Next
    Next
    Return result
End Function

En este caso, la función retorna todos los atributos asignados a un PropertyInfo (o sea, a una propiedad), y retorna nuestro NameValueList con los valores obtenidos.

Obtener miembros específicos por atributo de un tipo

Obtener las propiedades que poseen un determinado atributo.


public static List<PropertyInfo> GetPropertiesByAttribute(Type type, Attribute attribute)
{
    string s = attribute.GetType().ToString();
    var v = (from p in type.GetProperties() where (from aa in p.GetCustomAttributes(truewhere aa.ToString() == s select aa).Count() > 0 select p);
    List<PropertyInfo> l = new List<PropertyInfo>();
    l.AddRange(v);
    return l;
}


Public Shared Function GetPropertiesByAttribute(ByVal type As TypeByVal attribute As AttributeAs List(Of PropertyInfo)    Dim s As String = attribute.GetType.ToString
    Dim v = (From p As PropertyInfo In type.GetProperties
             Where (From aa In p.GetCustomAttributes(TrueWhere aa.ToString = s).Count > 0)
    Dim l As New List(Of PropertyInfo)
    l.AddRange(v)
    Return l
End Function

En este caso, la función se aplica sobre un tipo, del cual se obtienen las propiedades. De ellas, aquellas que tengan asignado un determinado atributo.

Obtener miembros específicos por nombre del atributo de un tipo


public static List<PropertyInfo> GetPropertiesByAttribute(
Type type, string attributeFullName)
    var v = (from p in type.GetProperties() where (HasAttribute(p, attributeFullName)) select p);
 
    List<PropertyInfo> l = new List<PropertyInfo>();    l.AddRange(v);
    return l;
}


Public Shared Function GetPropertiesByAttribute(ByVal type As TypeByVal attributeFullName As StringAs List(Of PropertyInfo)
    Dim v = (From p As PropertyInfo In type.GetProperties
            Where (HasAttribute(p, attributeFullName)))
    Dim l As New List(Of PropertyInfo)
    l.AddRange(v)
    Return l
End Function

En este caso, en lugar de utilizar el atributo propiamente dicho, se utiliza su nombre.

Comprobar clases.

Obtener los nombres de las clases que heredan de otra (o implementan una interfaz)


public static string[] GetClasesOfType<T>(System.Reflection.Assembly assembly)
{
    Type theI = typeof(T);
    var elems = (from el in assembly.GetTypes() where theI.IsAssignableFrom(el) select el.Name).ToArray();
    return elems;
}


Public Shared Function GetClasesOfType(Of T)(assembly As System.Reflection.AssemblyAs String()
    Dim theI As Type = GetType(T)
    Dim elems = (From el In assembly.GetTypes() Where theI.IsAssignableFrom(el) Select el.Name).ToArray()
    Return elems
End Function

Terminamos teniendo una nueva herramienta

Agregaremos entonces una clase a nuestra biblioteca de herramientas (Tools), que podríamos llamar, precisamente, Reflection, para exponer estos métodos.

Nótese que todos están declarados como static (Shared en VB), para poder utilizarlas sin necesitar crear una instancia de la clase.

En la próxima entrega, repasaremos el espacio de nombre Tools y lo complementaremos con más cositas

Atributos


Existen en .Net clases especiales que agregan indicadores funcionales a distintas partes del código, como Clases, métodos, propiedades, etc.

La intención de los mismos es permitir indicar características especiales a dichos elementos, como por ejemplo, en el caso de propiedades de controles, si son editables en tiempo de diseño, si se permite al usuario modificarlas, entre otras.

Otro ejemplo es la indicación de funcionalidades específicas, como por ejemplo, en un enumerador, cuando éste debe comportarse como “Mapa de bits” (atributo Flags).

Una característica interesante del propio .Net Framework es que es posible definir Atributos personalizados.

Se trata sencillamente de crear una clase que herede de System.Attribute

Los estándares de nomenclatura de .net Framework indican que el nombre de la clase debe terminar con Attribute. (Llamativamente, durante su utilización, esa parte del nombre no es necesaria). Por Ejemplo:

DataFieldAttribute

Por lo demás,al igual que cualquier otra clase, pueden tener propiedades, métodos, constructores, etc. aunque normalmente, no se implementa código ejecutable, si es posible hacer uso de los mismos con propiedades que definan características específicas de aquello a lo cual queremos aplicarle el atributo.

En el siguiente ejemplo, se define un atributo específico para propiedades, que indica que la misma se corresponde con un campo específico de una tabla de base de datos.Inclusive, en forma opcional, se podrá indicar que valor predeterminado utiliza (usando un enumerador).

Veamos el código,y luego analizaremos algunos detalles.

VB

Imports System.Runtime.CompilerServices
 
 
<AttributeUsage(AttributeTargets.[Property] Or AttributeTargets.Field, Inherited:=False, AllowMultiple:=False)> _
Public Class DataFieldAttribute
    Inherits Attribute
    Public Property ColumnName() As String
        Get
            Return m_ColumnName
        
End Get         Set(value As String)
             m_ColumnName
= value
        
End Set     End Property     Private m_ColumnName As String     Public Property DefaultValue() As DefaultValueEnum         Get             Return m_DefaultValue
        
End Get         Set(value As DefaultValueEnum)
             m_DefaultValue
= value
        
End Set     End Property     Private m_DefaultValue As DefaultValueEnum     Public Sub New(<CallerMemberName> Optional columnName As [String] = "", Optional defaultValue As DefaultValueEnum = DefaultValueEnum.None)
        
Me.ColumnName = columnName
        
Me.DefaultValue = defaultValue
    
End Sub End Class Public Enum DefaultValueEnum     Now
    AutoIncrement
    None
End Enum

C#

using System;
using System.Runtime.CompilerServices;
 
namespace DS.Common
{
     [
AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)]     public class DataFieldAttribute : Attribute     {
        
public string ColumnName
         {
            
get { return m_ColumnName; }
            
set { m_ColumnName = value; }
         }
        
private string m_ColumnName;
        
public DefaultValueEnum DefaultValue
         {
            
get { return m_DefaultValue; }
            
set { m_DefaultValue = value; }
         }
        
private DefaultValueEnum m_DefaultValue;
        
public DataFieldAttribute([CallerMemberName()] String columnName = "", DefaultValueEnum defaultValue = DefaultValueEnum.None)
         {
            
this.ColumnName = columnName;
            
this.DefaultValue = defaultValue;
         }
     }
    
public enum DefaultValueEnum     {
         Now,
         AutoIncrement,
         None
     } }

 

Puntos importantes.

  • La clase hereda efectivamente de Attribute. A su vez, tiene asignado un atributo indicando que puede aplicarse a Propiedades y/o Campos.  Además, que sólo puede aplicarse una vez en cada propiedad o campo.
  • Posee propiedades ColumnName y DefaultValue
  • DefaultValue es de un tipo también definido en el código, el enumerador DefaultValueEnum
  • Finalmente,el constructor admite dos argumentos, columnName y defaultValue, respectivamente, para asignar a las propiedades correspondientes.
  • Ambos parámetros son opcionales.  Para el valor predeterminado se asigna None, y para el nombre de la columna, una cadena vacía.
  • Sin embargo, en caso deno indicarse un valor cuando se instancia el atributo, el mismo usa una funcionalidad del compilador, el atributo CallerMemberName, que permite asignar el valor del elemento que lo llama (en este caso, el nombre de la propiedad o el campo), en lugar de la cadena vacía, con lo cual, en ningún caso tendría un valor vacío.

Espero pronto poder, finalmente, combinar distintos elementos que vimos, para crear algo más funcional.

Nos vemos pronto.

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

Serie de videos en Channel 9–Tipos de variables


Como el amigo Matías se ha tomado el trabajo de grabar vídeos con elementos importantes de .Net, les dejo aqui alguno para que repasen.

Matías Iacono

channel9logo_55552f9b

Otro video de la serie.

En esta oportunidad hablamos sobre los diferentes tipos de variables que podemos encontrar en C# y sus principales diferencias.

http://channel9.msdn.com/Blogs/DevWow/-100devdays-Tipos-y-Variables-en-C-

Ver la entrada original

interfaces


Cuando se trata de generalizar nuestro código, existe otra forma posible de hacer esto, que tiene algunas diferencias en cuanto a su implementación, pero además ciertos usos que nos permiten definir mecánicas, inclusive con actores esto es programadores, externos que usen otros desarrollos.

Es el caso de las interfaces. Por definirlo brevemente, un interfaz es ni más ni menos, un contrato que debemos cumplir nuestro código.

La diferencia fundamental entre un interfaz y una clase base, es que la interfaz en sí misma no contiene código de ejecución. Sencillamente lo que hace este definir cómo se verán los objetos desde fuera.

A diferencia de lo que sucede con una clase base, un objeto definido por nosotros, puede implementar varias interfaces distintas, mientras que sólo puede heredar de una clase base.

Esto ha llevado a muchas discusiones teóricas, dado que en varios entornos se pretenden heredades de múltiples clase base. Aun cuando esto podría ser útil en ciertos y determinados casos, desde mi punto de vista, esto hace que el mantenimiento de ese código resulte mucho más dificultoso.

Llegado el caso de pretenderse heredar de múltiples clases, lo que debiera hacerse es definir en cascada las herencias, de modo tal de poder implementar código del más básico hasta el más especializado, y no hacer que se hereden desde clases base distintas.

Así, en el ejemplo que estábamos utilizando, la clase Vegetal que hereda de la clase SerVivo, podría a su vez ser heredada, por una clase Árbol.

Esta podría definir nueva propiedades, como tipo de raíces, formato de la hoja, cantidad ramificaciones, etc. Y luego tendríamos otras clases que al heredar de Árbol, también estarían heredando en forma indirecta de Vegetal y también de SerVivo.

Uso de Interfaces

Una cosa distinta son las interfaces. Un interfaz es, en definitiva, una especificación de algo que se debe cumplir, esto es, que cada clase  que implementa dicha interfaz, obligatoriamente debe exponer todos y cada uno de los miembros que la interfaz define.

Es en ese sentido que decimos que la interfaz es un contrato. Al existir, una biblioteca de código que conozca la interfaz, podrá acceder a cualquiera de sus métodos, aún sin tener una referencia directa, de la biblioteca de clases que implementa dichos métodos.

Esto se suele utilizar, cuando no podemos tener conocimiento claro o relación directa, con el conjunto de código con el cual vamos a tratar.

Imaginemos que por ejemplo, estamos creando un proceso, que necesita utilizar datos obtenidos de distintos orígenes, y que no sabemos a ciencia cierta, ni cuáles serán, ni cómo serán implementados.

O mejor aún, que estamos creando un componente, que sea capaz de realizar ciertas tareas, y que luego otra programadores pueden utilizar, en futuras aplicaciones o implementaciones de los orígenes de datos.

Inclusive a futuro, otro programadores de otras empresas, podrían implementar sus propios orígenes de datos sin tener que referenciar todo nuestro código, bastando sencillamente que conocieran las interfaces correctas.

Así, las interfaces podrían definirse en una biblioteca, que podría entregarse a cualquier programador externo, para que haga sus propias implementaciones de orígenes de datos, que luego nuestro componente o aplicativo pudiese utilizar sin mayores inconvenientes.

Criterios de definición de un interfaz.

Estos son los criterios básicos para la definición de cualquier interfaz.

  • No contiene código ejecutable.
  • No se pueden definir alcances: todos los miembros declarados en un interfaz son en forma predeterminada, públicos.
  • Se pueden definir tanto propiedades como métodos; si la propiedad es de sólo lectura o sólo escritura quedará consignado en la declaración del interfaz, y luego las clase que implemente solo podrán exponer estos métodos.
  • En la declaración se podrán utilizar tipo de datos personalizados, como podrían ser enumeraciones, clases personalizadas, estructuras, etc. Sin embargo, en dicho caso será conveniente, que estos tipos de datos se encuentran declarados en la misma biblioteca de clases que las interfaces, para mantenerlas aisladas del resto de los componentes, y así poder distribuirlos sin mayores inconvenientes.

Ejemplo.

El siguiente ejemplo muestra cómo sería la interfaz IVida, que se correspondería con nuestra clase base de SerVivo, si quisiéramos definir sus miembros aisladamente de la clase base.

Por convención, se suele definir el nombre de una interfaz, comenzando con I mayúscula.

VB

Public Interface IVida
    Sub Nacer()
    Sub Crecer()
    Sub Reproducir()
    Sub Morir()
    Sub GenerarEnergía()
End Interface

CS

 

interface IVida
{
    void Nacer();
    void Crecer();
        
    void Reproducir();
    void Morir();
        
    void GenerarEnergía();
}

Implementando la interfaz

 

Una vez definida un interfaz, cualquier clase que quiera indicar que cumple este contrato, deberá implementar la interfaz.

Como suele suceder, diferentes lenguajes de programación, utilizan distintos mecanismos para indicar esto. Para Visual Basic existe una palabra reservada, implements, mientras que el C#, se define exactamente igual que una herencia esto es, indicando después del nombre de declaración de la clase, el caracter dos puntos (:) y el nombre de la interfaz o interfaces que se implementan,  separados por comas. En el caso que además de implementar interfaces, se quisiera heredar de otra clase, el César la clase de la cual se hereda debe estar inmediatamente luego del carácter dos puntos.

Así quedaría entonces, la clase vegetal heredando de SerVivo e implementando IVida.

VB

Public Class Vegetal
    Inherits SerVivo
    Implements IVida
    Private Sub Fotosíntesis() Implements IVida.GenerarEnergía
    End Sub
End Class

CS

public class Vegetal : SerVivo,IVida
{
   
    private void Fotosíntesis() 
    {
    }
    void IVida.GenerarEnergía()
    {
        Fotosíntesis();
    }

Deberíamos resaltar que, mientras en Visual Basic la implementación de cada miembro de la interfaz es declarativa, utilizando la palabra reservada “Implements”, en C# la implementación se hace por coincidencia de nombre. Es por eso que, en VB, el método FotoSíntesis puede implementar directamente el GenerarEnergía de la interfaz, mientras que en C# se necesitan ambos métodos, y hacer la llamada en cascada.

Finalmente, un método que Implementa una interfaz en Visual Basic, puede ser privado (y por tanto, solo visible desde la interfaz), mientras que, nativamente, en C# debe cumplir con el alcance.

Sin embargo, como se ve en el ejemplo, el método que implementará interfaz, puedas hacerlo implícitamente, utilizando sólo el nombre del método, o como en este caso, explícitamente antecediendo al nombre del método, el nombre de la interfaz seguido por un punto.

Al hacer la llamada en cascada, el método que realmente posee el código, en nuestro ejemplo FotoSíntesis, queda declarado  igualmente como privado, con lo cual estamos obteniendo el mismo resultado final.

Son sencillamente, diferentes formas de hacer la misma cosa.

En la siguiente publicación, comenzaremos a utilizar combinatoria as de estas características, en un conjunto de bibliotecas que nos permitan realizar finalmente, tareas útiles.

El blog de Dani Seara

Microsoft Azure Blog

El blog de Dani Seara

VIVIENDO RODANDO

de rodar con cámara a rodar en una silla

Matías Iacono

De los bits a la psiquis

Leandro Tuttini Blog

El blog de Dani Seara

WindowServer

El blog de los paso a paso

campusMVP.es

El blog de Dani Seara

Angel \"Java\" Lopez on Blog

Software Development, in the Third Millenium

Angel "Java" Lopez

El blog de Dani Seara

Atascado en los 70 II (El regreso)

Segunda época del rockblog "Atascado en los 70". VIEJAS canciones y artistas PASADOS DE MODA. Tratamos al lector de usted y escribimos "rocanrol" y "roquero" con ortografía castellana.

Santiago Porras Rodríguez

El blog de Dani Seara

Todo en ASP.NET

Blog dedicado al desarrollo Web y temas relacionado con tecnologías Microsoft.

El Bruno

Innovation Craftsman

Cajon desastre

Just another WordPress.com site

Pasión por la tecnología...

Todo sobre tecnología Microsoft en general, y SharePoint en partícular...

return(GiS);

Mi sitio geek