Archivo de la categoría: Nivel

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

 

Anuncios

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

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

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.

Hablemos de Herencia (2)


Ya hemos visto en Hablemos de Herencia, los conceptos básicos de este tema.

Analicemos entonces como queda el código de lo ya definido, y algunas variantes al respecto.

Ser vivo.

Por recordar, esta era su representación gráfica.

image

Y así quedaría el código:

VB

Public MustInherit Class SerVivo
    Public Sub Nacer()
    End Sub
    Public Sub Crecer()
    End Sub
    Public Sub Reproducir()
    End Sub
    Public Sub Morir()
    End Sub
    Public MustOverride Sub GenerarEnergía()
End Class

CS

public abstract class SerVivo
{
    public void Nacer()
    {
    }
    public void Crecer()
    {
    }
    public void Reproducir()
    {
    }
    public void Morir()
    {
    }
    public abstract void GenerarEnergía();
}

Como vemos, la clase se declara como que debe heredarse (MustInherit en VB, abstract en C#), y luego se definen los distintos métodos.

El método que debe codificarse en las clases derivadas, se declara como MustOverride (VB), abstract (C#).

Presta atención que, en C#, el mismo término significa cosas distintas, dependiendo del entorno. No es muy común, pero a veces pasa.

En ambos casos, los métodos que deben codificarse en los descendientes, no tienen cuerpo (No hay End Sub en VB, ni llaves en C#, donde termina con punto y coma).

Las clases derivadas.

image

VB

Public Class Vegetal
    Inherits SerVivo
    Public Overrides Sub GenerarEnergía()
        Fotosíntesis()
    End Sub
    Private Sub Fotosíntesis()
    End Sub
End Class

CS

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

Como vemos, ese método “no implementado” en la clase base, se debe terminar de definir en las clases derivadas (“sobre escribirse”).

En ambos lenguajes, la definición es casi idéntica.

En Visual Studio, al definir que una clase hereda de otra, el entorno te indica “como error” que deben escribirse esos métodos y hasta se ofrece a generarte el esquema base Sonrisa

Se indica que una clase hereda por distintos mecanismos, según el idioma.

En Visual Basic, se declara taxativamente que hereda “Inherits” de otra clase.

En C#, ese concepto se “entiende” porque luego de la declaración de la clase, se sigue con dos puntos, y el nombre de la clase que se hereda.

A partir de aquí, se podrá seguir heredando para cubrir, progresivamente, mayores especializaciones y características.

Lo interesante, es que cualquier modificación de una clase base, se reflejará (en realidad, se “heredará”), en cualquiera de sus derivadas.

Esto de la herencia, en definitiva, nos permite “comprometer” que nuestro código se vea siempre de la misma forma, en todas las clases derivadas/heredadas. Aún cuando una clase heredada presente más miembros, si la “vemos” como una clase base, siempre encontraremos los mismos métodos.

Una clase vegetal vista como ser vivo

Una clase animal vista como ser vivo

Pero hay otra forma de “comprometer” tu código para cumplir un convenio.

Eso es tema de la siguiente publicación.

Hablemos de Herencia


Una de las características más importantes de la programación orientada a objetos, es la posibilidad de estructurar mostró código en distintos niveles o capas, de modo tal de poder ir encapsulando funcionalidades desde las más básicas hasta las más complejas, incluyendo modificaciones en la funcionalidad de cualquiera en otros objetos, dependiendo de con quién están tratando o qué tipo de datos se van manipular o acciones a realizar.

Sin embargo, esto tiene una contrapartida; si no se piensan adecuadamente las cosas a implementar, se puede caer en una Maraña de códigos que van llamando funcionalidades hay into niveles y por tanto a ser que dicho código resulte un poco inmanejable, o inclusive que tienda a a incrementar las posibilidades de error.

Por tanto, lo más importante de todo, más allá de conocer las distintas te indica y sintaxis respecto escritura de código, es la comprensión clara de que se trata precisamente esto de la herencia.

Desde la realidad.

La mejor manera de comprender esto entonces, es observar que es lo que sucede dentro de la naturaleza. Así por ejemplo, podemos consideran desde el punto de vista biológico, que absolutamente cualquier ente vivo compre cuatro funciones básicas a saber:

  • Nacer
  • Crecer
  • Reproducirse
  • Morir

Cada reino y cada especie a su vez, podrán realizar cualquiera de estas actividades de distinta forma e inclusive podrán incrementar sus características, dependiendo del grado el colectivo.

Tomemos por caso la producción de energía: en todos los seres vivos este hecho en necesario, pero la forma de hacerlo es totalmente diferente cuando hablamos del reino vegetal y el reino animal. Entretanto agreguemos sencillamente esta funcionalidad,  imprescindible en cualquier ente vivo.

  • Generar energía.

Así, el reino vegetal utiliza en General la fotosíntesis, mientras que en el reino animal la generación de energía se produce por mecanismos de digestión de los alimentos. Para decirlo muy genéricamente, los vegetales toman sol, los animales combing.

 

Sin embargo, cualquiera estados acciones están pensadas para obtener el mismo resultado: energía.

 

Si fuésemos must identificó extraterrestre, lo que trataremos de investigar es precisamente, más en estas cosas que están en el planeta tierra para generar energía. Nos encontraríamos entonces que investigando la generación, veríamos dos mecanismos totalmente diferentes entre sí (en realidad no son tan diferentes desde un punto de vista bioquímico dado que termina en procesos bastante similares y que en alguna parte son los mismos).

Podríamos entonces considerar que, cualquiera sea vegetal o animal hereby características que son comunes: las cinco que hemos indicado con viñetas.

Cierto es también, que alguna de esas actividades por ejemplo generar energía, si bien desde fuera (científico extraterrestre) lo veríamos cómo una sola acción, internamente cada uno de los reinos lo estaría realizando de maneras diferentes.

Esto nos lleva a entender algunas definiciones respecto the la exposición de las funcionalidades desde nuestros objetos hacía observadores externos, o para darles un hombre más adecuado a nuestro entorno, llamadores or instanciadores.

Interfaz

Una interfaz es en definitiva, un contrato que los objetos confirman respecto de cómo se exponen a sí mismos y como brindan sus funcionalidades de modo tal que cualquier otro objeto que pretenda utilizar los tenga claro cuáles son los mecanismos y propiedades que podrá encontrar en dichos objetos.

La interfaz en definitiva, es una tipificación de que miembros, entendiendo como miembros el conjunto de propiedades, métodos y eventos, que expone un objeto y cuáles son las posibles formas de utilizarlos en cada caso. Se dice que cada miembro expone una firma o sea el nombre por el que se lo llamará, los argumentos que puede recibir, y el tipo de valor que retornará.

A veces el propio objeto será responsable de exponer su interfaz, mientras que en otros casos la interfaz será declarada en forma independiente y luego implementada por el objeto en cuestión.

 

Además, en términos de herencia, una clase que sirve como base para definir otras puede ser responsable de definir la interfaz que va a ser común a todas ellas, aunque a su vez las descendientes podrán tener miembros agregados que modifiquen o expandan la interfaz de cada uno de ellos.

Ejemplo

En conclusión entonces, podríamos crear una clase para el ejemplo que esté utilizando llamada SerVivo que tendría cinco métodos, como se muestra en el siguiente esquema.

image

Una segunda clase que hereda de esta llamada Vegetal, y que dentro del miembro genera energía utilizará los métodos de fotosíntesis.

image

Y otra clase que hereda también de SerVivo, llamada Animal que en el miembro generar energía utilizará procedimientos de digestión.

image

Algunas cuestiones de imagen.

Tenemos bastante para hablar respecto de cómo se representa las cosas, sin embargo para esta primera entrega quiero dejarle claro los elementos que estamos viendo en estos tres gráficos.

En principio un rectángulo de bordes redondeados representa una clase: lo sabemos además porque debajo del nombre precisamente aparece la palabra class.

Cada método está definido por debajo del grupo métodos, como un cubo con el nombre del método su lado. En el caso de la clase SerVivo, uno de ellos aparece en cursiva porque si bien la clase y lo define, no lo tiene implementado; delega la implementación en cada una de las clases que heredan de esta; decimos entonces que ese método debe heredarse, y normalmente una clase  que tiene métodos con estas características pasa a ser también una clase que obligadamente debe heredarse.

Finalmente, los métodos Fotosíntesis de la clase Vegetal y Digestión de la clase Animal están representadas como métodos con un cubo pero con el icono de un candado abajo la derecha: esto significa que ese método es privado a la clase y por tanto no es accesible desde fuera por parte de ningún otro objeto. Los miembros GenerarEnergía de Animal y de Vegetal internamente deberán llamar cada uno al que le corresponde.

image

En la representación gráfica del conjunto completo, la clase SerVivo tiene un indicador de que debe heredarse y  a su vez las clases Vegetal y Animal indican con una flecha hacia la derecha que heredan de SerVivo. Finalmente el diagrama General no representa esto gráficamente con las flechas que apunta desde las clases derivadas hacia la clase base

En la próxima entrega estaremos analizando cómo quedaría dicho código.

Encapsular en tus clases


Cualquier conjunto de instrucciones que crees, deben estar necesariamente dentro de un miembro (member). Estos conjuntos de código deben estar contenidos en algún archivo que representa una unidad de código.

En Visual Basic, existen los módulos, que pueden cumplir esta función. (En realidad, esto se hereda de las primeras implementaciones del lenguaje, donde los miembros que se utilizaban como genéricos, podían estar en módulos globales).

Por herencia, esa funcionalidad sigue existiendo para el .Net Framework, pero C# no dispone de esa forma de contenedor. Para C# solo existen clases.

Esto no es ni malo, ni bueno.

Si se convierte un módulo de VB, a C#, basta con definir una clase como estática… y lo demás será igual (bueno, los miembros también deben declararse como estáticos).

Por otra parte, en el propio Visual Basic, también es posible declarar métodos estáticos.. pero no tiene la funcionalidad de clase estática.. para ello, ya tiene el módulo.

Considera que, una clase estática, podría ser como una biblioteca (y aclaro, es biblioteca NO “librería” Sonrisa ), de tus funciones personalizadas “de uso directo”. Por ejemplo, si haces muchos programas financieros, podrías tener métodos estáticos para cálculos de interés, de amortización etc.

Sin embargo, en muchas otras oportunidades, una clase podrá representar una entidad de tu problemática y en dicho caso, podrás considerarlas como plantillas para crear distintas ocurrencias de dichas entidades.

Pero, en definitiva, una clase es una plantilla (template), que contiene código relacionado. 

En ella, los miembros podrán ser de distinto tipo:

Propiedades

Representan los “atributos” de cada entidad (instancia). Por ejemplo, si quiero representar un color, podría definir una clase que exponga las propiedades Rojo, verde y azul.

Definirlas como propiedades, permite que se puede ejecutar código dentro de ellas. Así, cuando se le asigna un valor, establecer un cálculo, o validar que el valor esté dentro de un determinado rango.

O asignar un valor predeterminado cuando  otro código reclama el valor.

Estamos entonces definiendo que, en realidad una propiedad es una combinatoria de dos miembros: el de asignar el valor, y el de obtener el valor.

VB

        Private m_Rojo As Integer
        Public Property Rojo() As Integer
            Get
                Return m_Rojo
            End Get
            Set(ByVal value As Integer)
                m_Rojo = value
            End Set
        End Property

CS

        int m_Rojo = 0;
        public int Rojo
        {
            get
            {
                return m_Rojo;
            }
            set
            {
                m_Rojo = value;
            }
        }

 

Si embargo, se puede obviar una de las dos partes.

Dejar la propiedad como de solo lectura.

VB

        Private m_Rojo As Integer
        Public ReadOnly Property Rojo() As Integer
            Get
                Return m_Rojo
            End Get
        End Property

CS

        int m_Rojo = 0;
        public int Rojo
        {
            get
            {
                return m_Rojo;
            }
        }

 

Dejar la propiedad como de solo  escritura

VB

        Private m_Rojo As Integer
        Public WriteOnly Property Rojo() As Integer

            Set(ByVal value As Integer)
                m_Rojo = value
            End Set
        End Property

CS

        int m_Rojo = 0;
        public int Rojo
        {
            set
            {
                m_Rojo = value;
            }
        }

 

O definir la propiedad sin asignar código específico.

VB

Public Property Rojo As Integer

CS

        public int Rojo { get; set; }

Campos.

Son formas especiales de propiedades, que se basan en simplemente definir la variable, su tipo y su “exposición” (que nivel de otros objetos pueden utilizarlo).

Por lo demás, son variables dentro de la clase.

        Public Verde As Integer

        public int Verde;

Métodos.

Son conjuntos de código para ejecutar.

Podemos sub clasificarlos entre los que no retornan ningún valor (procedimientos), y los que si retornan un valor (funciones).

En C#, en realidad, todos retornan un valor (o sea, son funciones). Pero en el caso de no necesitar que retorne nada, se declara el tipo que se retorna como void.

 

        Sub HacerAlgo()

        End Sub
        Function RetornarAlgo() As Integer

        End Function

        public void HacerAlgo()
        {
        }
        public int RetornarAlgo()
        {
        }

Así mismo, un método admite que se le pasen argumentos:

        Sub HacerAlgo(argumento As String)

        End Sub

        public void HacerAlgo(string argumento)
        {
        }

Los argumentos pueden ser opcionales, en cuyo caso, se requiere que se le asigne un valor predeterminado. además, una vez que se declara un argumento como opcional, todos los que siguen deben serlo.

        Sub HacerAlgo(Optional argumento As String = "Valor")

        End Sub

        public void HacerAlgo(string argumento="Valor")
        {
        }

O declarar un conjunto de argumentos de los que no se conoce la cantidad. En este caso, se declara como vector, arreglo (Array), para que se asignen todos los que hagan falta.

        Sub HacerAlgo(ParamArray argumentos() As String)

        End Sub

        public void HacerAlgo(params string[] argumentos)
        {
        }

Dejamos para más adelante, definir otras cosas, como los eventos.

Pero con estos elementos, ya podremos empezar a crear cosas útiles.

Incluido en el envase… (del .Net Framework)


Realizar programas es como cocinar.  (Idea que he “robado” al amigo Miguel Egea, @miguelEgea https://twitter.com/miguelEgea). Combinar distintos ingredientes para obtener un resultado novedoso.

Comenzando con las sentencias, tipos de datos, variables que hemos visto previamente, hasta componentes pre armados “listos para usar”

Sin embargo, mucho de ese código debe realizar tareas complejas, de mucho cuidado, como manipular posiciones de memoria, acceder a recursos de hardware o sencillamente, realizar operaciones complicadas.

Esos conjuntos de códigos vienen pre armados y encapsulados en el .Net Framework,

El .Net Framework.

Podríamos describir el .Net Framework como constituido por 3 partes:

  • El motor de ejecución común (CLR).
  • El Conjunto de clases base
  • Los lenguajes de programación compatibles (en realidad, no solo los lenguajes, sino también sus compiladores)

El Motor de Ejecución Común.

Diagrama del CLR

Como vemos en la imagen, hay una serie de componentes que integran el CLR de modo de implementar en forma encapsulada, las acciones de bajo nivel.

Para destacar que hay un componente responsable de “cargar” los tipos (clases) en memoria asegurándose de darles el espacio suficiente, asignarle un identificador único, etc.

O el compilador de MSIL (que merece una aclaración): Para permitir que el mismo código pueda ejecutarse en distintas configuraciones de computación, éste no se compila en código binario sino en un “lenguaje intermedio” que luego, cuando se comienza a ejecutar, se termina de compilar a código nativo.  Aunque parezca más laborioso y demorado, la realidad es que es imperceptible para el usuario. Además, si realmente es preocupante este hecho (que no se justifica realmente), existe, entre las herramientas del .Net Framework un “compilador a código nativo” que se puede ejecutar en la máquina destino, para dejarlo ya listo. (NGEN.exe).

Otra parte interesante es el GC (Garbage collector, o recolector de residuos). Una de las problemáticas  en los anteriores mecanismos de programación de C++ a Visual Basic, era la posibilidad de “descuidar” las instancias de los objetos creados, que dejaban de ser referenciados por el código del programa… pero nunca se descargaba de memoria. El GC se encarga de ver revisar cada instancia y ver si “hay algún código en ejecución que la conozca”. Si no, se encarga de eliminarla. (En realidad, si la clase utilizara recursos de bajo nivel, primero se encarga de disponer dichos recursos para que no sigan en uso, y luego, descargar la instancia de la memoria). Otra consecuencia de esto, que se debe tener en cuenta, es que si un proceso crea una instancia de una clase, es factible que se le entregue una que está “en espera” en el GC para ser destruida, que es más rápido. Por ello se recomienda que no se asuma que las variables tengan valores predeterminados.  Es importante inicializar las variables en cada creación.

La biblioteca de Clases Base.

Biblioteca de Clases Base

Son todas aquellas clases que nos permiten interactuar directa o indirectamente con los recursos electrónicos de la máquina, memoria, procesador, periféricos, disco, y otros más complejos, pero, en todos los casos, el fundamento sobre el que se basa toda la ejecución.

Por ejemplo, los tipos de datos están todos definidos allí.

Vemos que además de las BCL, existen otros conjuntos ya pre armados, de funcionalidades específicas como manejo de datos, manipulación de XML, y especializaciones para funcionalidades de alto nivel, como formularios de ventanas de Windows, aplicaciones Web, etc.

La siguiente es una lista de los conjuntos de funcionalidades más importantes incluidas en la BCL.

  • Administración de componentes Web que corren tanto en el servidor como en el cliente (ASP.NET)
  • Administración de memoria
  • Aleatoriedad
  • Auto descripción de código
  • Cifrado de datos
  • Compilación de código
  • Generación de código
  • Herramientas de despliegue de gráficos (GDI+)
  • Herramientas de seguridad e integración con la seguridad del sistema operativo
  • Interacción con el API Win32 o Windows API.
  • Interacción con los dispositivos periféricos
  • Interacción con otras aplicaciones
  • Manejo de arreglos de datos y colecciones
  • Manejo de cadenas de caracteres y expresiones regulares
  • Manejo de datos (ADO.NET)
  • Manejo de idiomas
  • Manejo de tipos de datos unificado
  • Manejo del sistema de ventanas
  • Manejo y administración de excepciones
  • Manipulación de archivos de imágenes
  • Manipulación de fechas, zonas horarias y periodos de tiempo
  • Operaciones aritméticas
  • Transmisión y recepción de datos por distintos medios (XML, TCP/IP)

Pero.. ¿Que es una clase?.

En definitiva es la plantilla que incluye variables, propiedades, métodos y eventos relacionados con una funcionalidad específica. 

Cuando necesitas usar una, creas una instancia y luego comienzas a utilizarla.

Existe además una forma especial de clases de instanciación automática, que sirven para realizar tareas. En ellas, cuando se utiliza uno de sus miembros (propiedad o método), se instancia sola y sirve para cualquier otro uso durante la ejecución del programa completo. Se denominan clases estáticas.

Espacios de Nombres. (Namespaces)

Teniendo tantas variedades y posibilidades, se podría transformar en una maraña incontrolable el conjunto de código.

Para mantener el orden, se agrupan en espacios de nombres, que además pueden ser jerárquicos.

Así, por ejemplo, las clases que permiten conectarse con un servidor de datos SQL Server están en:

System.Data.SqlClient

Esto permite además agrupar conjuntos de clases por “autor” (veremos más adelante que los permisos de ejecución de un componente pueden administrarse basados en espacios de nombres y/o ensamblados, que veremos ahora mismo.

Así, cosas que Microsoft ha hecho específicos, están dentro de un espacio de nombres que comienza precisamente por Microsoft como por ejemplo:

Microsoft.Win32.SafeHandles

… y también podremos crear los nuestros.

Ensamblados.

Ensamblado NET

Uno o más espacios de nombres, se compilan en bibliotecas de carga dinámica (archivo de extensión DLL),  que ante tantas combinatorias posibles, necesitan ser más identificables, requieren descriptores de sus contenidos, pueden utilizar textos, imágenes y otros recursos que se utilizan por las clases. El conjunto de todos esos elementos se denomina Ensamblado.

Las descripciones se contienen en un manifiesto, que describen unívocamente el ensamblado completo, y los metadatos de tipo, que contienen la descripción de cada una de las clases (recordemos, una clase es la plantilla de in Tipo), contenidas en el ensamblado.

El ensamblado completo queda entonces contenido dentro del archivo dll.

Un detalle: Nada condiciona que un espacio de nombres este contenido en un ensamblado. así, tipos distintos de un mismo espacio de nombres, pueden estar contenidos en distintos ensamblados. Eso asegura extensibilidad en la funcionalidad. Si yo necesito, digamos, un par de clases del espacio de nombres “espacio1” debo definir una referencia al ensamblado (y su correspondiente dll) que la contiene.

Luego, si necesito otra clase, puede ser que “No la encuentre” dado que su funcionalidad, más específica, se haya incluido en otro ensamblado (aunque pertenezca al mismo espacio de nombres). Deberé entonces agregar una referencia al otro ensamblado.

 

En la próxima entrega, empezaremos ya a recorrer las BCL más importantes y generar código de herramientas útiles en general.