¡Hola! Amigos de Axialis software me comentan que andan de ofertas navideñas…
Te recomiendo visitar https://www.axialis.com/
¡Que te sea útil!
¡Hola! Amigos de Axialis software me comentan que andan de ofertas navideñas…
Te recomiendo visitar https://www.axialis.com/
¡Que te sea útil!
Cuando hacemos bibliotecas en general, resulta muy difícil poder abarcar todas las posibilidades, por mucho que pensemos, y analicemos.
Sin ir más lejos, esta última semana me encontré con que tenía que hacer algunos cambios en nuestra biblioteca common, para facilitar serializar (almacenar binariamente, en este caso, ya que serializando a XML no necesitaba modificar nada). Pero de eso hablaremos después.
El punto es que, tarde o temprano, modificaremos nuestro código, lo cual no está para nada mal… se llama evolución J
Esto nos lleva a la necesidad tener previstos mecanismos para comprobar que los cambios no afecten las funcionalidades que ya teníamos aseguradas y probadas.
Cuanto más evolucionemos nuestras bibliotecas, más complejos serán los pasos para volver a probar que todo funcione como esperamos.
Es por ello que existen los proyectos de tipo prueba, que incluye no solo esta opción (probar bibliotecas) sino que abarca pruebas de interfaz, de rendimiento, etc.
Pero, hoy, hablaremos de pruebas para nuestras funciones, o Pruebas Unitarias
Es de considerar que un mismo proyecto de pruebas, podrá aplicarse a múltiples bibliotecas, e inclusive hasta es conveniente, dado que nos permitirá automatizar pruebas múltiples en el tiempo.
Procedamos, pues, a agregar un proyecto de pruebas.
El proyecto expone contiene clases de pruebas unitarias, marcadas como TestClass (por atributo).
Además, cada método que debe cumplir una prueba, se decora con el atributo TestMethod.
Sencillamente, porque un motor de pruebas, embebido en Visual Studio, reconoce dicha clases y métodos, para ejecutarlos cada vez que se requiera hacer las pruebas.
Team Foundation Server (Y Visual Studio Online), poseen motores de pruebas parecidos.
Cada método de prueba es un procedimiento, que hace lo necesario para llamar una función (o inclusive más).
Dicho método debe reportar al motor de pruebas si ésta fue satisfactoria. Y esto se hace utilizando Assert (Afirmar, aseverar), que dispone de varios métodos de respuesta lógica, como “Es Igual”, “Es Distinto”, etc.
Así el motor sabe, cuando las pruebas se realizan en modo automatizado, el resultado en cada caso.
Veamos por ejemplo, una prueba unitaria de nuestra biblioteca de recursos.
[TestClass]
public class Resources
{
[TestMethod]
public void GetString()
{
string result = DS.Resources.Resources.ErrorTitle;
string lang = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
Assert.AreEqual(result, lang==“en” ? “Operation error”: “Error en la operación”);
}
}
Finamente, podemos probar individualmente un método, e inclusive depurar el proceso de prueba, en el entorno de Visual Studio, con el menú contextual que aparece en cualquiera de los métodos marcados como de prueba.
Al ejecutarse las mismas, el Explorador de Prueba, en Visual Studio, nos presenta los resultados.
En la imagen se muestran las pruebas de varios métodos de todas las bibliotecas de las que hemos hablado.
Una de las características importantes de las aplicaciones en el mundo globalizado es la necesidad de utilizar recursos para poder localizar mensajes, textos y representaciones gráficas acordes a cada posible usuario.
Esto incluye, como es lógico, cualquier comunicación que parta desde nuestras bibliotecas.
Sin embargo, considerando a futuro, el agregado de idiomas, resulta bastante engorroso mantener archivos de recursos en cada biblioteca que definamos.
¿Por qué no tener centralizados los mismos en una sola biblioteca?
Las herramientas que nos permiten manejar archivos de recursos en nuestros proyectos, generan clases que exponen dichos recursos pero que no se exponen a otras bibliotecas (no son públicas).
Lo primero que se nos ocurriría sería agregarles el modificador public, pero, el generador de Visual Studio re crea la clase completa ante cada cambio y, por tanto, deberíamos ir a modificarla en cada ocasión. L
Nos hace falta otro mecanismo que mantenga una clase, pública, que exponga todos nuestros recursos aun cuando agreguemos o eliminemos algunos.
Y para ello, nos podemos apoyar en la transformación de Texto.
T4 aparece en Visual Studio 2010 y es en lo que se basó la generación de clases a partir del modelo de datos de una base SQL Server, en Entity Framework.
(del tema de EF… mejor hablamos en otra ocasión) L
El concepto es simple: tienes una plantilla que se usa para generar código (o páginas HTML, o texto plano), la cual puedes usar para reconstruir clases, o generar textos basados en datos (como reportes sencillos), o cosas asi.
En este caso, la idea es simple: crear una clase que exponga como público un recurso, y utilizarlo luego como plantilla para, recorriendo los miembros definidos en un archivo de recursos re exponerlos como públicos.
Para mantener orden en nuestro desarrollo, definamos una nueva biblioteca de clases, dentro de la cual, definimos un archivo de recursos con un nombre específico para poder identificar la clase generada fácilmente.
|
|
El espacio de nombres será DS.Resources, y el archivo de recursos (y su clase derivada) CommonResources.
Entre todos los componentes que podemos encontrar como extensión, tenemos una que facilita la edición de los recursos para múltiples idiomas.
Además de permitir definir los mismos en más de un idioma (y agregar idiomas a posteriori), facilita la exportación e importación de los mismos, para obtener traducciones desde profesionales del tema.
Esta clase que usaremos como ejemplo, expone una de los recursos contenidos en el archivo CommonResources desde una clase pública.
Public Class Resources
Public shared ReadOnly Property Copyright As String
Get
Return My.Resources.CommonResources.Copyright
End Get
End Property
End Class
using DS.Resources;
using System;
namespace DS.Resources
{
public static class Resources
{
public static String Copyright { get
{ return CommonResources.Copyright; } }
}
}
Agregamos ahora a nuestro proyecto, un archivo de tipo Plantilla de Texto
(Existen dos tipos de plantilla de texto. Hay una específica para que sea utilizada por tu proyecto en tiempo de ejecución. En este caso, no es esa).
El nombre que le des a ese archivo, será el que tendrá la clase luego. Por tanto, a este archivo le llamamos Resources.
Pegamos el código de la clase en este archivo, que utilizaremos para reemplazar los valores de nombre de la propiedad y tipo de datos, por los que recuperaremos de los definidos en la clase CommonResources
Public Class Resources
Public shared ReadOnly Property <#= sName #> As <#= sType #>
Get
Return My.Resources.CommonResources.<#= prop.Name #>
End Get
End Property
End Class
Explicando, los elementos encerrados entre <# y #> son código ejecutable en tiempo de transformación, mientras que el resto, se toman literalmente.
Utilizando Reflection (si, de nuevo), y el modelo de objetos de Visual Studio, podemos obtener los elementos definidos en el archivo de recursos, e incluirlos en un bucle con este código, para generar cada una de las propiedades públicas.
Este sería el código completo de la plantilla.
<#@ template debug="true" hostspecific="true" language="VB" #>
<# ' Define which assemblies must be imported #>
<#@ assembly name="System.Core" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".vb" #>
Public Class Resources
<#
dim serviceProvider as IServiceProvider= Me.Host
dim dte as EnvDTE.DTE = serviceProvider.GetService(gettype(EnvDTE.DTE))
dim item = dte.Solution.FindProjectItem(Host.TemplateFile).ContainingProject
dim OutPutFile as string= item.Properties("FullPath").value & "bin\debug\" & item.Properties("OutputFileName").Value
Dim ass As System.Reflection.Assembly = System.Reflection.Assembly.LoadFrom(OutPutFile)
Dim t As Type = ass.GetType("DS.Resources.My.Resources.CommonResources")
Dim props = (From el In DirectCast(t, System.Reflection.TypeInfo).DeclaredProperties Where Not {"ResourceManager", "Culture"}.Contains(el.Name)).ToList
For Each prop As System.Reflection.PropertyInfo In props
dim sName as string=prop.Name
dim sType as string=prop.PropertyType.Name
#>
Public shared ReadOnly Property <#= sName #> As <#= sType #>
Get
Return My.Resources.CommonResources.<#= prop.Name #>
End Get
End Property
<#
Next
ass=nothing
#>
End Class
<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
using DS.Resources;
using System;
namespace DS.Resources
{
public static class Resources
{
<#
IServiceProvider serviceProvider = (IServiceProvider)this.Host;
EnvDTE.DTE dte = (EnvDTE.DTE) serviceProvider.GetService(typeof(EnvDTE.DTE));
var item = dte.Solution.FindProjectItem(Host.TemplateFile).ContainingProject;
string OutPutFile = item.Properties.Item("FullPath").Value + "bin\\debug\\" + item.Properties.Item("OutputFileName").Value;
System.Reflection.Assembly ass = System.Reflection.Assembly.LoadFrom(OutPutFile);
Type t = ass.GetType("DS.Resources.CommonResources");
System.Reflection.TypeInfo tInfo=(System.Reflection.TypeInfo)t;
string[] noAdd = new string[] { "ResourceManager", "Culture" };
var props = (from el in ((System.Reflection.TypeInfo)t).DeclaredProperties where !noAdd.Contains(el.Name) select el).ToList();
foreach( System.Reflection.PropertyInfo prop in props)
{
string sName =prop.Name;
string sType =prop.PropertyType.Name;
#>
public static <#= sType #> <#= sName #> { get { return CommonResources.<#= sName #>; } }
<#
}
#>
}
}
<#@ template debug=”true” hostspecific=”true” language=”xx” #>
Determina que se trata de una plantilla, a la cual podemos querer depurar, y que es específica para el “host” (en este caso, el entorno de Visual Studio. En esta plantilla en particular es importante, ya que necesitamos obtener información del proyecto donde se encuentra la plantilla. En forma predeterminada este valor viene en falso, y por tanto, nos daría un error al tratar de acceder a información del proyecto, como veremos luego.
Necesitamos incluir este ensamblado, dado que vamos a obtener información del entorno de desarrollo (ese ensamblado aplica precisamente, a comunicarse con el propio entorno de Visual Studio).
Obtiene la instancia actual del entorno de desarrollo de Visual Studio.
Obtiene el Proyecto actual y de este, genera en la variable OutPutFile, el camino completo a la DLL resultante de la compilación del proyecto.
Cargamos por reflexión dicha dll, y obtenemos la definición de la clase CommonResources, que es la que contiene nuestros recursos.
En la variable Props, obtenemos todas las propiedades definidas en dicha clase, excluyendo, las dos que siempre se definen en los archivos de recursos: ResourceManager y Culture, dado que no son definidas por nosotros.
Luego, sencillamente, recorremos esa lista, y generamos las distintas propiedades de la clase pública.
Al grabar la plantilla, el entorno transformará el mismo generando la clase.
Nota: por estar generando código dinámicamente, el entorno alerta de dicha situación, por temas de seguridad
Quedando el resultado como el siguiente ejemplo.
Public Class Resources
Public Shared ReadOnly Property Copyright As String
Get
Return My.Resources.CommonResources.Copyright
End Get
End Property
Public Shared ReadOnly Property ErrorTitle As String
Get
Return My.Resources.CommonResources.ErrorTitle
End Get
End Property
Public Shared ReadOnly Property FKError As String
Get
Return My.Resources.CommonResources.FKError
End Get
End Property
Public Shared ReadOnly Property NameValueList_MissingType As String
Get
Return My.Resources.CommonResources.NameValueList_MissingType
End Get
End Property
Public Shared ReadOnly Property NotAllowedBySystem As String
Get
Return My.Resources.CommonResources.NotAllowedBySystem
End Get
End Property
End Class
using System;
namespace DS.Resources
{
public static class Resources
{ public static String Copyright { get { return CommonResources.Copyright; } } public static String ErrorTitle { get { return CommonResources.ErrorTitle; } } public static String FKError { get { return CommonResources.FKError; } } public static String NameValueList_MissingType { get { return CommonResources.NameValueList_MissingType; } } public static String NotAllowedBySystem { get { return CommonResources.NotAllowedBySystem; } }
}
}
Un detalle: si revisas el código resultante, verás que no es similar. Esto es porque en los dos lenguajes, la ubicación por espacio de nombres es diferente. En VB, los recursos se incluyen dentro del espacio de nombres especial My, que se refiere al proyecto completo, que en C# no existe.
Pero, para los objetivos buscados, son idénticos.
Hasta la próxima, donde probaremos estas cosas. (Adecuadamente)
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).
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:
Agregamos algunos atributos que seguramente usaremos a futuro (para implementar, por ejemplo, modelos de datos)
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.
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:
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
Donde nos ponemos a investigar un poco que tienen nuestros objetos.
Donde nos ponemos a investigar un poco que tienen nuestros objetos.
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 .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:
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) |
En muchos casos, utilizaremos LinQ sobre objetos para obtener información (lo cual facilita la codificación y acelera el proceso).
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 String) As 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.
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 PropertyInfo) As 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.
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(true) where 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 Type, ByVal attribute As Attribute) As 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(True) Where 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.
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 Type, ByVal attributeFullName As String) As 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.
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.Assembly) As 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
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
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.
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
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
} }
Espero pronto poder, finalmente, combinar distintos elementos que vimos, para crear algo más funcional.
Nos vemos pronto.
31 de enero, en Bogotá.
https://www.facebook.com/events/1411248562501195/?notif_t=plan_user_invited
Puedes anotarte en http://1drv.ms/1G228ib
¡Que salga genial!
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.
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)
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)
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)
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) )
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
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)
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
El equipo de Visual Basic de Microsoft ha vuelto a las andadas y han publicado una interesante lista de trucos acerca de como compartir código entre aplicaciones Windows 8 y Windows Phone.
¡Imperdible!
http://blogs.msdn.com/b/vbteam/archive/2014/06/15/vb-universal-app-part-1-getting-started.aspx
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.
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
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
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
#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; } }
El blog de Dani Seara
de rodar con cámara a rodar en una silla
De los bits a la psiquis
El blog de Dani Seara
El blog de los paso a paso
El blog de Dani Seara
Software Development, in the Third Millenium
El blog de Dani Seara
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.
El blog de Dani Seara
Innovation Craftsman
Just another WordPress.com site
Todo sobre tecnología Microsoft en general, y Office 365 y SharePoint en partícular...
Mi sitio geek
El blog de Dani Seara