CodeDom
Hoy comentaré acerca de uno de los tópicos menos conocidos dentro del mundo de .NET, y que a mí personalmente me apasiona por el potencial de explotación que nos brinda. Y aunque existen varios posts en la red que hablan acerca de esto, aun es poca la difusión que tiene, ya que en mi opinión todo programador debería tener una mínima referencia a este tema.
CodeDom (Code Document Object Model) es un conjunto de clases agrupados en el namespace System.CodeDom, que nos proporciona la funcionalidad necesaria para representar tipos .NET a partir del mapping correspondiente con las clases de CodeDom. Este subsistema se basa en arboles binarios para crear una representación de grafo de nuestro código. Una vez que tenemos el grafo de código, podemos indicarle cualquiera de los proveedores de código para obtener la representación específica. Un proveedor de código, es aquel que se encarga de transformar ese grafo de tipos a código especifico, por ejemplo C#, VB, y cualquier otro lenguaje para el que exista un proveedor
La generación de código es un concepto muy viejo, tanto como las diferentes soluciones que se han implementado, desde la concatenación de cadenas hasta llegar a CodeDom, pero también la orientación que se le ha dado, algunas de los usos que se le ha dado va desde la generación y compilación dinámica de código en Runtime, hasta la creación de ORM (Object Relational Mapping). Estos últimos son, para mí, la orientación más interesante y es en la que basare mi ejemplo de hoy.
El problema que intenta resolver los ORM es básicamente tratar de crear objetos que interactúen entre distintos ambientes, es decir, intermediarios.
Diferentes tipos de datos
Aunque cada tecnología cuenta con diferentes tipos de datos y a veces con diferentes implementaciones para cada uno de ellos, un tipo de dato siempre corresponde a una unidad lógica fundamental en la estructura de representación de información en un entorno dado. Por ejemplo SQL Server, C#, XML
Puesto que casi siempre (en la mayoría de los casos), tenemos información acerca de los tipos de datos con los que trabajamos (sobre todo en ambientes strongly-typed como .NET), independientemente de la tecnología. Por ejemplo, cuando trabajamos con SQL, conocemos información acerca de las columnas, como el tipo, longitud, si es foreign key, si es primary key, si es valor único, etc. Esto nos permite hacer una relación de tipos entre diferentes ambientes de trabajo para la generación de mappers, y así, crear una representación respectiva de una entidad
Por ejemplo dada la siguiente tabla de SQL Server
Alumnos
Deberíamos poder generar automáticamente las clases que interactuaran con el acceso, persistencia y entidades contenedores de datos, ya que tenemos suficiente información para inferir el mapeo correspondiente a cada uno de estos tipos “desconocidos” para el entorno .NET.
Al final una simple clase contenedora de un row de la tabla de Alumnos podría verse así:
namespace Blog.CodeDom.GeneratedEntities
{
using System;
public class Alumno
{
private Int32 _idAlumno;
private String _nombre;
private String _apellidoPaterno;
private String _apellidoMaterno;
private String _direccion;
private Int32 _idSalon;
private Int32 _idEdificio;
public Alumno()
{
}
public Alumno(Int32 _idAlumno, String _nombre, String _apellidoPaterno, String _apellidoMaterno, String _direccion, Int32 _idSalon, Int32 _idEdificio)
{
this._idAlumno = _idAlumno;
this._nombre = _nombre;
this._apellidoPaterno = _apellidoPaterno;
this._apellidoMaterno = _apellidoMaterno;
this._direccion = _direccion;
this._idSalon = _idSalon;
this._idEdificio = _idEdificio;
}
public virtual Int32 IdAlumno
{
get
{
return this._idAlumno;
}
set
{
this._idAlumno = value;
}
}
public virtual String Nombre
{
get
{
return this._nombre;
}
set
{
this._nombre = value;
}
}
public virtual String ApellidoPaterno
{
get
{
return this._apellidoPaterno;
}
set
{
this._apellidoPaterno = value;
}
}
public virtual String ApellidoMaterno
{
get
{
return this._apellidoMaterno;
}
set
{
this._apellidoMaterno = value;
}
}
public virtual String Direccion
{
get
{
return this._direccion;
}
set
{
this._direccion = value;
}
}
public virtual Int32 IdSalon
{
get
{
return this._idSalon;
}
set
{
this._idSalon = value;
}
}
public virtual Int32 IdEdificio
{
get
{
return this._idEdificio;
}
set
{
this._idEdificio = value;
}
}
}
}
Vamos a analizar con más detenimiento el código que genera esta salida.
Creamos un objeto CodeTypeDeclaration, que es el objeto que utilizamos para crear la definición de la clase Alumno
CodeTypeDeclaration classAlumno = new CodeTypeDeclaration("Alumno");
classAlumno.Attributes = MemberAttributes.Public;
Agregamos cada una de las propiedades con sus respectivos campos de la clase, para esto he definido un método de ayuda. Existen dos clases principales utilizadas en este paso, CodeMemberField, que representa un campo y CodeMemberProperty que representa una propiedad
Al final agregamos dos constructores, uno sin parámetros y el otro con el mismo número de campos de la clase.
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("Int32", "_idAlumno", "IdAlumno"));
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_nombre", "Nombre"));
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_apellidoPaterno", "ApellidoPaterno"));
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_apellidoMaterno", "ApellidoMaterno"));
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_direccion", "Direccion"));
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("Int32", "_idSalon", "IdSalon"));
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("Int32", "_idEdificio", "IdEdificio"));
public static CodeTypeMemberCollection CreateFieldAndProperty(string typeofProperty, string fieldName, string propertyName)
{
CodeMemberField field = new CodeMemberField(typeofProperty, fieldName);
field.Attributes = MemberAttributes.Private;
CodeMemberProperty property = new CodeMemberProperty();
property.Name = propertyName;
property.Type = new CodeTypeReference(typeofProperty);
property.Attributes = MemberAttributes.Public;
property.GetStatements.Add(new CodeMethodReturnStatement()
{
Expression = new CodeFieldReferenceExpression()
{
TargetObject = new CodeThisReferenceExpression(),
FieldName = field.Name
}
}
);
property.SetStatements.Add(new CodeAssignStatement()
{
Left = new CodeFieldReferenceExpression()
{
TargetObject = new CodeThisReferenceExpression(),
FieldName = field.Name
},
Right = new CodePropertySetValueReferenceExpression()
}
);
CodeTypeMemberCollection members = new CodeTypeMemberCollection();
members.Add(field);
members.Add(property);
return members;
}
Como habrán visto, cada uno de estos miembros de la clase son agregados a la variable classAlumno, que es el objeto contenedor, por así decirlo, de objetos como CodeMemberProperty, CodeMemberField, CodeConstructor, CodeTypeConstructor, CodeMemberMethod, etc. Y el objeto classAlumno es agregado al objeto CodeNamespace
CodeNamespace nm = new CodeNamespace("Blog.CodeDom.GeneratedEntities");
nm.Types.Add(classAlumno);
Lo interesante hasta aquí es que en código que hemos escrito, aun no hemos especificado ningún lenguaje en el cual será convertido nuestro grafo de código, si bien es cierto el ejemplo está escrito en C#, esto no quiere decir que el código generado al final deba ser C#, podría ser de VB (aunque no me guste pondré como se vería la salida en VB)
Imports System
Namespace Blog.CodeDom.GeneratedEntities
Public Class Alumno
Private _idAlumno As Int32
Private _nombre As [String]
Private _apellidoPaterno As [String]
Private _apellidoMaterno As [String]
Private _direccion As [String]
Private _idSalon As Int32
Private _idEdificio As Int32
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal _idAlumno As Int32, ByVal _nombre As [String], ByVal _apellidoPaterno As [String], ByVal _apellidoMaterno As [String], ByVal _direccion As [String], ByVal _idSalon As Int32, ByVal _idEdificio As Int32)
MyBase.New()
Me._idAlumno = _idAlumno
Me._nombre = _nombre
Me._apellidoPaterno = _apellidoPaterno
Me._apellidoMaterno = _apellidoMaterno
Me._direccion = _direccion
Me._idSalon = _idSalon
Me._idEdificio = _idEdificio
End Sub
Public Overridable Property IdAlumno() As Int32
Get
Return Me._idAlumno
End Get
Set(ByVal value As Int32)
Me._idAlumno = value
End Set
End Property
Public Overridable Property Nombre() As [String]
Get
Return Me._nombre
End Get
Set(ByVal value As [String])
Me._nombre = value
End Set
End Property
Public Overridable Property ApellidoPaterno()
As [String]
Get
Return Me._apellidoPaterno
End Get
Set(ByVal value As [String])
Me._apellidoPaterno = value
End Set
End Property
Public Overridable Property ApellidoMaterno()
As [String]
Get
Return Me._apellidoMaterno
End Get
Set(ByVal value As [String])
Me._apellidoMaterno = value
End Set
End Property
Public Overridable Property Direccion() As
[String]
Get
Return Me._direccion
End Get
Set(ByVal value As [String])
Me._direccion = value
End Set
End Property
Public Overridable Property IdSalon() As Int32
Get
Return Me._idSalon
End Get
Set(ByVal value As Int32)
Me._idSalon = value
End Set
End Property
Public Overridable Property IdEdificio() As Int32
Get
Return Me._idEdificio
End Get
Set(ByVal value As Int32)
Me._idEdificio = value
End Set
End Property
End Class
End Namespace
Como había comentado antes, la especificación del proveedor, es el que, al final del día, nos proporcionará la transformación a los diferentes lenguajes. Y solo queda mostrar el código completo con el que se especifica el proveedor de código.
CodeNamespace nm = Blog.CodeDom.Samples.CodeDomManager.CreateClassAlumno();
nm.Imports.Add(new CodeNamespaceImport("System"));
CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider();
provider.GenerateCodeFromNamespace(nm, Console.Out, new CodeGeneratorOptions() { BracingStyle="C" });
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.CodeDom;
namespace Blog.CodeDom.Samples
{
public partial class CodeDomManager
{
public static CodeNamespace CreateClassAlumno()
{
CodeNamespace nm = new CodeNamespace("Blog.CodeDom.GeneratedEntities");
CodeTypeDeclaration classAlumno = new CodeTypeDeclaration("Alumno");
classAlumno.Attributes = MemberAttributes.Public;
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("Int32", "_idAlumno", "IdAlumno"));
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_nombre", "Nombre"));
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_apellidoPaterno", "ApellidoPaterno"));
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_apellidoMaterno", "ApellidoMaterno"));
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("String", "_direccion", "Direccion"));
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("Int32", "_idSalon", "IdSalon"));
classAlumno.Members.AddRange(CodeDomHelper.CreateFieldAndProperty("Int32", "_idEdificio", "IdEdificio"));
classAlumno.Members.Add(CodeDomHelper.CreateDefaultConstructor(classAlumno.Name));
CodeDomHelper.CreateMembersConstructor(classAlumno);
nm.Types.Add(classAlumno);
return nm;
}
}
}
Espero que este post ayude a que los desarrolladores volteen la mirada a este namespace tan olvidado y despierte en ellos la curiosidad y la inventiva para desarrollar nuevas soluciones basadas en estos temas.
Happy Coding!