lunes, 5 de enero de 2009

Haciendo Generadores de Codigo con CodeDom

A los que están interesados en realizar sus propios generadores de código en .NET (C# o VB.NET), les dejo un ejemplo para utilizar Codedom (los imports mínimos son: System.CodeDom y System.CodeDom.Compiler )
Lo bueno de CodeDom es que permite generar código de manera genérica pero de implementación concreta, esto es, podemos hacer generadores de código para cualquier de los lenguajes de .NET (c++, C#, Vb.NEt, etc).

Aquí vá el ejemplo que pueden utilizar y probar, este código genera un string con el código de una clase en C# y en VB, con una propiedad llamada ID y un constructor predeterminado. Ideal para comenzar a probar y experimentar.

Dim ns As CodeNamespace 'definiendo un namespace para nuestra clase

Private Sub GeneraClase
'defino un namespace base
ns = New CodeNamespace("PrimerCodeDom")

'agrego los imports correspondientes
'aca pueden agregar todos los imports necesarios
ns.Imports.Add(New CodeNamespaceImport _
("System"))
ns.Imports.Add(New CodeNamespaceImport _
("System.Diagnostics"))
ns.Imports.Add(New CodeNamespaceImport _
("System.Text"))

'creo la clase en si misma
Dim clase As New CodeTypeDeclaration("Clase_Clientes")
'agrego la clase a la colección de tipos
ns.Types.Add(clase)

'defino un campo privado

Dim campo As New CodeMemberField("integer", "_ID")
campo.Attributes = MemberAttributes.Private

'agrego el miembro privado a la clase

clase.Members.Add(campo)

'defino un campo público

Dim p As New CodeMemberProperty()

' le doy un nombre a la propiedad
' ID en este caso
p.Name = "ID"

' lo hago público
p.Attributes = MemberAttributes.Public
p.Type = New CodeTypeReference("System.Int32") 'es integer
p.HasGet = True
p.HasSet = True 'si fuese false, sería solo lectura

'esto que viene es obligatorio
'tengo que decirle que cosa devuelve el get de la propiedad
p.GetStatements.Add( _
New CodeMethodReturnStatement( _
New CodeFieldReferenceExpression( _
New CodeThisReferenceExpression(), "_ID")))
'esto también es obligatorio
'tengo que decirle como se asigna el valor del set
p.SetStatements.Add(New CodeDom.CodeAssignStatement( _
New CodeFieldReferenceExpression( _
New CodeDom.CodeThisReferenceExpression, "_ID"), _
New CodeDom.CodePropertySetValueReferenceExpression))

'agrego la propiedad a la clase
clase.Members.Add(p)

'defino un constructor público

Dim constructor As New CodeConstructor()
constructor.Attributes = MemberAttributes.Public
clase.Members.Add(constructor)

'defino los providers para los lenguajes en los que voy a generar mi clase
Dim csProvider As New Microsoft.CSharp.CSharpCodeProvider
Dim vbprovider As New VBCodeProvider

'en .net 3.5 este código tira un warning por obsoleto,
'pero igual funciona perfecto.
Dim codigo As ICodeGenerator

codigo = vbprovider.CreateGenerator()
Dim vbCod As String = generaCode(codigo)

codigo = csProvider.CreateGenerator
dim csCod as String = generaCode(codigo)

'en este punto, en las variables vbCod y csCod
'tienen el código de la clase correctamente generada.

End Sub

'metodo complementario
Private Function generaCode(ByVal CodeGenerator As ICodeGenerator) As String

Dim options As New CodeGeneratorOptions()

' defino la identación del código
options.IndentString = Space(3)

' creo un StringWriter
Dim sb As New StringBuilder()
Dim sw As StringWriter = New StringWriter(sb)

' genero
CodeGenerator.GenerateCodeFromNamespace(ns, sw, options)

' devuelvo el string con el código generado
Return sb.ToString()
End Function

Una utilidad interesante podría ser utilizar este código para generar un mapeador de clases contra tablas de una base de datos, para lo cual debemos recorrer las tablas de una base de datos, ver código SQL aquí: http://sqldata.blogspot.com/2009/01/como-obtener-todas-las-tablas-de-una.html y por cada tabla recorrer los campos con el Script SQL listado aquí: http://sqldata.blogspot.com/2009/01/como-obtener-todos-los-campos-de-una.html y aplicar el codigo codedom correspondiente para generar un mapeador de clases contra tablas.
O sea, por cada tabla generar la clase en codedom y por cada campo generar dentro de la clase correspondiente las propiedades que necesitemos.

Con eso tienen lo necesario para generarse un mapeador de clases si se ponen manos a la obra.


Hugo Bernachea