martes, 20 de julio de 2010

LINQ2Sql: Modificar dinámicamente el nombre de una tabla en un DataContext de LINQ.

Ya van varias veces que veo preguntas de cómo hacer para cambiar dinámicamente el nombre de una tabla de un mapeo de LINQ.

Algunas aplicaciones crean nuevas tablas dinámicamente a medida que, por algún motivo, las van necesitando. Estas nuevas tablas se crean a partir de una tabla molde o "base".
Dicho de otra manera: Se tiene una base de datos en la que n tablas tienen el mismo esquema y sólo difieren en su nombre y, se desea poder mapear dichas tablas con un único DataContext que pueda ser instanciado dinámicamente según la tabla que se quiera consultar.

La misma idea se puede aplicar para el nombre de una base de datos o cualquier otro objeto de SQL.

NOTA: Los ejemplos de esta entrada están en inglés porque inicialmente fue la respuesta de este thread de MS

EL PROBLEMA:

Por defecto, el mapeo de LINQ se lleva a cabo mediante atributos y, como tales, no pueden ser modificados en tiempo de ejecución.
Pero por suerte el constructor de la clase DataContext de LINQ tiene una sobrecarga con el parámetro mappingSource, que nos permite obtener el DataContext a partir de un mapeo externo (que puede ser un archivo .xml).

CASO DE EJEMPLO:

Se tiene la tabla base Person, y otras dos tablas más llamadas Person_1 y Person_2, todas con idéntica estructura.

A continuación se lista el script que crea estas tablas:


Use [DB_Test]
GO
Create Table dbo.Person
(
ID int PRIMARY KEY,
Name nvarchar(MAX)
)
Create Table dbo.Person_1
(
ID int PRIMARY KEY,
Name nvarchar(MAX)
)
Create Table dbo.Person_2
(
ID int PRIMARY KEY,
Name nvarchar(MAX)
)
Insert Into dbo.Person Values (1, 'Base Table')
Insert Into dbo.Person_1 Values (1, 'Some data in Table_1')
Insert Into dbo.Person_2 Values (1, 'Some data in Table_2')

SOLUCIÓN:

La solución propuesta es mapear una única vez el esquema de la tabla y al momento de obtener el DataContext, especificar el nombre de la tabla que se quiere consultar.

Lo primero que necesitamos es generar un archivo .dbml con el mapeo de la tabla "base" de la forma habitual (Add New Item -> LINQ to SQL classes).
Como nombre le pondremos Person.dbml, por lo cual la clase generada se llamará PersonDataContext.
El diagrama quedará así:
dbml

Una vez generado el mapeo de la tabla base, procedemos a generar a partir de éste, un archivo .xml que también contendrá el mapeo.
Para esto utilizaremos la herramienta sqlmetal.
En línea de comandos de Visual Studio (Visual Studio Tools -> Visual Studio 2008 Command Prompt), ejecutar desde la carpeta donde se encuentra el archivo person.dbml:
SqlMetal /map:person.xml /code person.dbml
Esto generará el archivo person.xml cuyo contenido será algo como:


<?xml version="1.0" encoding="utf-8"?>
<Database Name="DB_Test" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">
<Table Name="dbo.Person" Member="Persons">
<Type Name="Person">
<Column Name="ID" Member="ID" Storage="_ID" DbType="Int NOT NULL" IsPrimaryKey="true" />
<Column Name="Name" Member="Name" Storage="_Name" DbType="NVarChar(MAX)" />
</Type>
</Table>
</Database>


A continuación, es recomendable incluir el archivo person.xml en nuestro proyecto como recurso embebido, con las propiedades Build Action=Enbedded Resource y Copy to Output Directory=Do not copy, como se muestra a continuación:
add

Ahora necesitamos generar un método que retorne el DataContext apuntando a una tabla en específico.
Lo más cómodo es codificarlo en la misma clase (PersonDataContext).
La forma más directa es parándonos sobre Person.dbml en el solution explorer y haciendo click en el botón View Code. Esto generará el archivo Person.cs con la clase PersonDataContext a la que le agregamos el método y queda así:


namespace DynamicDataContext
{
 using System.Linq;
 using System.Xml;
 using System.Xml.Linq;
 using System.Data.Linq.Mapping;
 using System.IO;
 using System.Reflection;
 partial class PersonDataContext
 {
  private const string CNN_STRING = "Data Source=.;Initial Catalog=DB_Test;Integrated Security=True";
  public static PersonDataContext GetDataContext(string tableName)
  {
   // Get the .xml file into memory 
   Stream ioSt = Assembly.GetExecutingAssembly().GetManifestResourceStream("DynamicDataContext.person.xml");
   XElement xe = XElement.Load(XmlReader.Create(ioSt));
   // Replace the table name value in memory
   var tableElements = xe.Elements().AsQueryable().Where(e => e.Name.LocalName.Equals("Table"));
   foreach (var t in tableElements)
   {
    var nameAttribute = t.Attributes().Where(a => a.Name.LocalName.Equals("Name"));
    foreach (var a in nameAttribute)
    {
     if (a.Value.Equals("dbo.Person"))
     {
      a.Value = a.Value.Replace("Person", tableName);
     }
    }
   }
   // Obtain and return the dynamic DataContext
   XmlMappingSource source = XmlMappingSource.FromXml(xe.ToString());
   return new PersonDataContext(CNN_STRING, source);
  }
 }
}
Y eso es todo…
Cada vez que necesitemos el datacontext, llamaremos al método GetDataContext recién creado pasándole el nombre de la tabla que nos interesa en ese momento:


static void Main()
{
 // Query table Person_2 
 using (PersonDataContext dc = PersonDataContext.GetDataContext("Person_2"))
 {
  Console.WriteLine(dc.Persons.First().Name);
 }
 // Query table Person_1
 using (PersonDataContext dc = PersonDataContext.GetDataContext("Person_1"))
 {
  Console.WriteLine(dc.Persons.First().Name);
 }
 // Query table Person
 using (PersonDataContext dc = PersonDataContext.GetDataContext("Person"))
 {
  Console.WriteLine(dc.Persons.First().Name);
 }
 // Query default table (Person in this case)
 using (PersonDataContext dc = new PersonDataContext())
 {
  Console.WriteLine(dc.Persons.First().Name);
 }
 Console.ReadKey();
}


-- Puedes bajar este ejemplo aquí --


Suerte !

Federico.

viernes, 9 de julio de 2010

Project Euler #144 solución en VB.NET

Resolución de ejercicio #144 de Project Euler en VB.NET.



Este es un interesante ejercicio que simula un sistema de espejos que reflejan un laser.

El ejercicio consiste en averiguar cuántas veces el haz de un laser rebota dentro de una elipse antes de salir por un pequeño orificio de entrada/salida ubicado en la parte superior.

Una forma de resolverlo es con un método (algoritmo, función) que, a partir del punto de colisión (sobre el que incide el laser en la elipse), calcule el próximo punto de colisión del haz siguiendo las leyes de la reflexión.
El proceso continúa hasta que el punto de colisión obtenido pertenezca al orificio de entrada.










Para resolverlo, se utilizaron dos clases simples: RectaR2 y PuntoD.
La primera representa a una recta en el plano y la segunda representa a un punto en el plano:

'Clase para representar a una recta en el plano
Public Class RectaR2
 Public P As PuntoD
 Public m As Double
 'A partir de la pendiente y un punto incluido en la recta
 Sub New(ByVal m As Double, ByVal P As PuntoD)
  Me.m = m
  Me.P = P
 End Sub
 'A partir de dos puntos de la recta
 Sub New(ByVal P1 As PuntoD, ByVal P2 As PuntoD)
  Dim m As Double = (P2.Y - P1.Y) / (P2.X - P1.X)
  Me.m = m
  Me.P = P2
 End Sub
 'Ecuacion Explicita
 Public Overrides Function ToString() As String
  Return String.Format("y = {0}x + {1}", Me.m, Me.P.Y - Me.m * Me.P.X)
 End Function
End Class
'Clase para representar un punto en el plano
Public Class PuntoD
 Public X As Double, Y As Double
 Sub New()
 End Sub
 Sub New(ByVal x As Double, ByVal y As Double)
  Me.X = x
  Me.Y = y
 End Sub
End Class
También se utilizó un método que denominé Paso, que calcula el siguiente punto de colisión del laser a partir del punto de colisión actual.

Este método es el que contiene los cálculos trigonométricos necesarios para obtener la reflexión del laser.


'A partir de un punto de inicio y fin de un rayo laser (ambos en la elipse)
'retorna el proximo punto de contacto del laser (tambien en la elipse)
Function Paso(ByVal Pini As PuntoD, ByVal Pfin As PuntoD) As PuntoD
 Dim SgtePunto As New PuntoD()
 'Calculo la pendiente de la recta tangente a la elipse en el Punto de Fin (m = 4x/y)     (m1)
 Dim m1 As Double = -4 * Pfin.X / Pfin.Y
 'Obtengo la pendiente de la recta reflejada (reflejo sobre la recta tangente)... Y = m2*X + b
 'tmp = (m0-m1)/(1+m0*m1)         m2 = (m1-X)/(1+X*m1)
 Dim m0 As Double = (Pfin.Y - Pini.Y) / (Pfin.X - Pini.X)
 Dim tmp As Double = (m0 - m1) / (1 + m0 * m1)
 Dim m2 As Double = (m1 - tmp) / (1 + tmp * m1)
 'Obtengo los 2 puntos de interesección entre la recta reflejada y la elipse
 Dim b As Double = Pfin.Y - Pfin.X * m2
 Dim CoordsX As PuntoD = Cuadratica(4 + (m2 ^ 2), 2 * m2 * b, (b ^ 2) - 100)
 'Descartar el punto fin (Pfin)
 SgtePunto.X = If(Math.Abs(CoordsX.Y - Pfin.X) < 0.001, CoordsX.X, CoordsX.Y)
 SgtePunto.Y = m2 * SgtePunto.X + b
 Return SgtePunto
End Function
También se utilizó una forma para poder graficar el recorrido del laser en la pantalla:


Public Class Form1
 Private bitmap As Bitmap = New Bitmap(500, 1000)
 Sub New()
  ' This call is required by the Windows Form Designer.
  InitializeComponent()
  'Dibujar la elipse
  Using g As Graphics = Graphics.FromImage(bitmap)
   g.DrawEllipse(Pens.Blue, g.VisibleClipBounds)
  End Using
 End Sub
 'Convertir un punto del plano en un punto de la forma
 Public Function ConvertirPunto(ByVal P0 As PuntoD) As PointF
  Return New PointF(CSng(250 + P0.X * 50), CSng(500 - P0.Y * 50))
 End Function
 'Dibujar un segmento de recta dado
 Public Sub DibujarRecta(ByVal P0 As PuntoD, ByVal P1 As PuntoD)
  Using g = Graphics.FromImage(bitmap)
   g.DrawLine(Pens.Red, ConvertirPunto(P0), ConvertirPunto(P1))
   Me.PictureBox1.Image = bitmap
  End Using
 End Sub
 'Dibujar un número dado en la parte inferior
 Public Sub DibujarNum(ByVal num As Integer)
  Dim pt1 As New PointF(450, 970)
  Using g = Graphics.FromImage(bitmap)
   g.FillRectangle(Brushes.LightGray, pt1.X, pt1.Y, 50, 30)
   g.DrawString(num.ToString, New Font("Verdana", 14.0!), Brushes.Black, pt1)
   Me.PictureBox1.Image = bitmap
  End Using
 End Sub
End Class
Y por último, la entrada principal de la aplicación, donde está el ciclo que efectúa la búsqueda de puntos de incidencia, el conteo y la orden de dibujar en pantalla:


Sub Main()
 Dim PIni As New PuntoD(0, 10.1)
 Dim PFin As New PuntoD(1.4, -9.6)
 Dim frm As New Form1
 Dim temp As PuntoD
 Dim steps As Integer = 0
 frm.Show()
 While PFin.Y < 0 OrElse Math.Abs(PFin.X) > 0.01
  frm.DibujarRecta(PIni, PFin)
  frm.DibujarNum(steps)
  Application.DoEvents()
  temp = PIni
  PIni = PFin
  PFin = Paso(temp, PFin)
  steps += 1
 End While
 MessageBox.Show("Euler144=" & steps)
End Sub
Espero a alguien le sea útil... Y sino, no importa...



-- Puedes bajar este ejemplo aquí --

viernes, 29 de mayo de 2009

Limitar la cantidad de instancias de una aplicación con VB.NET

He aquí una clase para limitar la cantidad de instancias que pueden correrse de una aplicación.

- Funciona si se cambia el nombre del ejecutable o si se ejecuta desde diferentes ejecutables.
- Funciona si la aplicación se cierra de forma anormal.
- Es configurable por Máquina o por Sesión (útil para limitar por usuario cuando se utilizan Terminal Services).

'The-Pirat 2009
Imports System.Threading

''' <summary>
''' Enumerador para inicar el tipo de bloqueo
''' </summary>
Public Enum eTipo As Integer
  POR_SESION = 0
  [GLOBAL] = 1
End Enum

''' <summary>
''' Clase para implementar un máximo M de instancias de la aplicación por cada usuario o por cada máquina
''' </summary>
Public Class InstanciaPrevia
  Private Shared _mutex As Mutex
  ''' <summary>
  ''' Función que devuelve TRUE si ya se rebasó el límite de instancias del programa corriendo
  ''' </summary>
  ''' <param name="Tipo">Tipo del bloqueo. Por usuario o global.</param>
  Public Shared Function RebasaLimite(ByVal Tipo As eTipo, ByVal Max As Integer) As Boolean
    Dim NombreAssembly As String = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name
    For i As Integer = 0 To Max - 1
      If CrearMutex(NombreAssembly, i, Tipo) Then
        Return False
      End If
    Next
    Return True
  End Function

  ''' <summary>
  ''' Intenta crear un nuevo mutex, devuelve true si NO existía previamente y se creó ahora
  ''' </summary>
  Private Shared Function CrearMutex(ByVal Nombre As String, ByVal Index As Integer, ByVal Tipo As eTipo) As Boolean
    'Nombre del mutex según Tipo (visibilidad)
    Dim mutexName As String = If(Tipo = eTipo.GLOBAL, "Global\", "Local\") & Nombre & Index.ToString()
    Dim newMutexCreated As Boolean
    Try
      'Abro/Creo mutex con nombre único
      _mutex = New System.Threading.Mutex(False, mutexName, newMutexCreated)
      If Not newMutexCreated Then
        _mutex.Close()
      End If
      Return newMutexCreated
    Catch ex As Exception
      Return False
    End Try
  End Function
End Class


La forma de utilizar esta clase es:


'Sólo se corre la aplicación si no se rebasa el límite, en este caso, de 3 instancias
Sub main()
  If Not InstanciaPrevia.RebasaLimite(eTipo.GLOBAL, 3) Then
    Application.Run(Form1)
  Else
    MessageBox.Show("Se pasó del límite")
    Application.Exit()
  End If
End Sub

Puedes bajar este ejemplo aquí



Suerte

lunes, 9 de marzo de 2009

Juego de la vida de Conway con Small Basic

Small Basic es una version reducida del Basic .NET de Microsoft, está enfocado a entornos educativos y su meta fundamental es convertir la programación en algo divertido y sencillo.

Desde aquí se puede bajar el Small Basic totalmente gratis.

La interfaz es muy intuitiva:



A continuacion se plasma el código para SmallBasic de la simulación del juego de la vida de Conway:

'ThePirat 2009
GraphicsWindow.PenColor = "Red"
GraphicsWindow.BrushColor = "Blue"
GraphicsWindow.BackgroundColor="white"
GraphicsWindow.MouseDown = OnMouseDown
GraphicsWindow.KeyDown = OnKeyDown
GraphicsWindow.Show()

SIZE = 30
Randomize()

RunLoop:
Draw()
Paso()
Program.Delay(1)
Goto RunLoop

Sub Randomize
For i = 0 To (SIZE * SIZE - 1)
If (Math.GetRandomNumber(5) > 3) Then
Array.SetValue("arr", i, 1)
Else
Array.SetValue("arr", i, 0)
EndIf
Endfor
EndSub

Sub Draw
GraphicsWindow.Clear()
w = GraphicsWindow.Width
h = GraphicsWindow.Height
For i = 0 To (SIZE * SIZE - 1)
val = Array.GetValue("arr", i)
If (val = 1) Then
GraphicsWindow.FillEllipse(w / SIZE * Math.Remainder(i+1, SIZE), h / SIZE * (Math.Floor(i / SIZE)), w / SIZE, h / SIZE)
'GraphicsWindow.SetPixel(Math.Remainder(i+1, SIZE), Math.Floor(i / SIZE), "Blue")
EndIf
EndFor
EndSub


Sub Paso
'Col = 1 if first column, Col = 2 if last column, Col = 0 otherwise
'Row = 1 if first row, Row = 2 if last row, Row = 0 otherwise
For i = 0 To (SIZE * SIZE - 1)
Alive = 0
COL = 0
ROW = 0
If (Math.Remainder(i, SIZE) = 0) Then
COL = 1 'First col
Endif
If (Math.Remainder(i + 1, SIZE) = 0) Then
COL = 2 'Last col
EndIf
If (i<SIZE) Then
ROW = 1 'First row
EndIf
If (i>=(SIZE*SIZE-SIZE)) Then
ROW = 2 'Last row
EndIf

If (COL <> 1 And ROW <> 1) Then 'not 1st row, not 1st col
Alive = Alive + Array.GetValue("arr", i - (SIZE + 1)) 'Up-Left
Endif
If (ROW <> 1) Then 'Not 1st row
Alive = Alive + Array.GetValue("arr", i - SIZE) 'Up
EndIf
If (COL <> 2 And ROW <> 1) Then 'Not mast col, not 1st row
Alive = Alive + Array.GetValue("arr", i - (SIZE - 1)) 'Up-Right
EndIf
If (COL <> 1) Then 'Not 1st col
Alive = Alive + Array.GetValue("arr", i - 1) 'Left
EndIf
If (COL <> 2) Then 'Not last col
Alive = Alive + Array.GetValue("arr", i + 1) 'Right
EndIf
If (COL <> 1 And ROW <> 2) Then 'Not 1st col, not last row
Alive = Alive + Array.GetValue("arr", i + (SIZE - 1)) 'Down-Left
EndIf
If (ROW <> 2) Then 'Not last row
Alive = Alive + Array.GetValue("arr", i + SIZE) 'Down
EndIf
If (COL <> 2 And ROW <> 2) Then 'Not last col, not last row
Alive = Alive + Array.GetValue("arr", i + (SIZE + 1)) 'Down-Right
EndIf

actual = Array.GetValue("arr", i)
newValue = actual
If (actual = 0 And Alive = 3) Then
newValue = 1
EndIf
If (actual = 1 And Alive <> 2 And Alive <> 3) Then
newValue = 0
EndIf

Array.SetValue("arrTmp", i, newValue)
Endfor

'Copy "arrTmp" into "arr"
For k = 0 To (SIZE * SIZE - 1)
Array.SetValue("arr", k, Array.GetValue("arrTmp", k))
EndFor
EndSub

Sub OnMouseDown
Randomize()
EndSub
Sub OnKeyDown
Randomize()
EndSub


Aquí se muestra la captura del programa corriendo:





Esta versión es muy simple y sólo permite configurar el tamaño de la grilla cambiando el valor de la variable SIZE.

Existen versiones más completas del juego de la vida para SmallBasic en este foro

Suerte !!!

miércoles, 4 de febrero de 2009

Red Gate Reflector para Crackear a Red Gate SQL Compare

Cómo usar el "Red Gate .NET reflector" para crackear al "Red Gate SQL Compare 6" en 10 pasos


Requisitos:
Instalar el .NET Reflector (que ahora es de Red Gate, pero originalmente de Lutz Roeder) de http://www.red-gate.com/products/reflector/

Instalar el Add-In "Reflexil" para el .NET Reflector de http://sourceforge.net/project/showfiles.php?group_id=200895

Instalar la versión Trial de Red Gate SQL Compare 6 de https://www.red-gate.com/dynamic/downloads/downloadform.aspx?download=sqltoolbelt

1) instalar el .NET Reflector y su Add-In Reflexil en la misma carpeta y luego desde el Reflector agregar el Add-In desde el menú View, Add-Ins




2) Abrir en el Reflector, la dll del Red Gate SQL Compare 6 que se encarga de evaluar las licencias: C:\Program Files\Red Gate\SQL Compare 6\RedGate.Licensing.Client.dll


Ahora lo que vamos a hacer es ubicar el método get_Activated, que devuelve True en caso de que el software esté activado


3) Navegar en el arbol de la izquierda hasta: RedGate.Licensing.Client, License, Activated, get_Activated(), darle doble click para desemsamblar


4) Abrir el Reflexil (Menú Tools, Reflexil)


Ahora vamos a modificar el código del método para que devuelva siempre True

5) Eliminar la segunda línea del código MSIL como se muestra a continuación:


6) De las dos instrucciones que quedaron, editar la primera como se muestra a continuación:

7) Cambiar el código de la instrucción por el código ldc.i4.1 y darle Update, como se muestra a continuación:


Listo... sólo nos falta guardar la DLL modificada, para eso


8) Navegar hacia arriba en el arbol de la izquierda hasta el nodo RedGate.Licensing.Client.dll, el Add-In del Reflexil cambiará su interfaz por una que tiene dos botones. Darle click al botón Save As... Como se muestra a continuación:

9) Por defecto guardará la dll con nombre RedGate.Licensing.Client.Patched.dll por lo cual habría que reemplazar la librería original RedGate.Licensing.Client.Dll y cambiarla por ésta.


10) Al guardar aparecerá un mensaje, se le puede dar en Cancel and Keep it Delay Signed y no habrá problemas.


Suerte...

El presente artículo sólo tiene fines didácticos, si quieres seguir utilizando el software de Red Gate, debes comprar la versión full luego de pasados los 14 días...

jueves, 15 de enero de 2009

Aplicación suicida en VB.NET - Programa que se elimina a sí mismo

He aquí una curiosa forma de hacer que un ejecutable se elimine a sí mismo:

Module Module1
Sub Main()
Suicide()
End Sub
Sub Suicide()
Dim EXEName As String = VB6.GetEXEName() & ".exe"
MakeBat(EXEName)
Dim Info As ProcessStartInfo = New ProcessStartInfo()
Info.Arguments = "/C deleter.bat"
Info.WindowStyle = ProcessWindowStyle.Hidden
Info.CreateNoWindow = True
Info.FileName = "cmd.exe"
Process.Start(Info)
End Sub
Private Sub MakeBat(ByVal EXEName As String)
Dim sb As New System.Text.StringBuilder
sb.AppendLine("@echo off")
sb.AppendLine(":loopb")
sb.AppendLine("del """ & EXEName & """ > nul")
sb.AppendLine("IF EXIST """ & EXEName & """ goto loopb")
sb.AppendLine("del deleter.bat")
My.Computer.FileSystem.WriteAllText("deleter.bat", sb.ToString(), False)
End Sub
End Module


Se debe agregar una referencia y el Imports a Microsoft.VisualBasic.Compatibility

lunes, 1 de diciembre de 2008

Convertir un campo público a propiedad Visual Studio - VB y C#

Cómo hacer para convertir automáticamente un campo (variable) a una propiedad pública y utilizarlo con una combinación de teclas en el Visual Studio 2008.

La idea es crear una macro que se encargue de hacer la conversión de la línea actual de código donde estemos parados y poder ejecutar esta macro con alguna combinación de teclas.

Por ejemplo, queremos convertir esta línea:
Public id As Integer

En esto:
Private _id As Integer
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal value As Integer)
_id = value
End Set
End Property
NOTA: En Visual Studio 2010 será mucho más simple la declaración de propiedades en VB, pareciéndose más a la declaración en C#

Lo primero que se necesita es abrir el explorador de macros (Menú View, Other Windows, Macro Explorer) o bien con ALT+F8.
En la nueva ventana, hacer click derecho en MyMacros y seleccionar "New Module", ponerle como nombre thepirat, y nuevamente click derecho (ahora sobre el nuevo módulo recién creado) y seleccionar "New Macro".
A continuación aparecerá el editor de macros y estaremos situados dentro de un método que seguramente se llama Sub Macro1() adentro del módulo llamado thepirat.

Adentro de este módulo, copiar y reemplazar TODO el código por el siguiente código
 

'The-Pirat 2008
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics
Imports System.Text.RegularExpressions

'http://msdn.microsoft.com/en-us/library/envdte.textselection_members.aspx

Public Module thepirat
'Declaro la expresión regular
Private Const REGEX_Declaracion_VB As String = _
"(public|private|dim)\s+(?'var'\w+)\s+as\s+(?'tipo'.*)"
Private Const REGEX_Declaracion_CSHARP As String = _
"(public\s|private\s|internal\s)?(?'tipo'.+)\s+(?'var'\w+)\s*;"

''' <summary>
''' Convierte un campo en propiedad pública.
''' Acepta la forma: Dim variable as tipo, public v as t, private v as t
''' </summary>
''' <remarks></remarks>
Public Sub campo2prop()
'Creo un punto de UNDO
DTE.UndoContext.Open("UndoCampoPropiedad")
Try
'Obtengo el documento activo
Dim doc = CType(DTE.ActiveDocument, EnvDTE.Document)
'Obtengo la selección del documento activo
Dim ts As TextSelection = doc.Selection
'Selecciono la línea completa
ts.SelectLine()
'Determino si es VB o C#
Dim bVB As Boolean = EsVisualBasic(doc.Name)
'Obtengo el texto de la selección (la línea)
Dim sLinea As String = ts.Text.Trim()
'Obtengo el nombre de la variable y su tipo
Dim sVar As String = "", sTipo As String = ""
ObtenerVariableYtipo(bVB, sLinea, sVar, sTipo)
'Si fue exitoso, imprimo la propiedad
ts.Insert(DevolverPropiedad(bVB, sVar, sTipo))
Catch ex As Exception
MsgBox(ex.Message)
Finally
'Cierro el punto de UNDO
DTE.UndoContext.Close()
End Try
End Sub

'Determina si es VB o C#
Private Function EsVisualBasic(ByVal nombre As String) As Boolean
If (nombre.EndsWith(".cs") Or nombre.EndsWith(".cpp") Or nombre.EndsWith(".h") Or nombre.EndsWith(".idl") Or nombre.EndsWith(".jsl")) Then
Return False
ElseIf (nombre.EndsWith(".vb")) Then
Return True
Else
Throw New Exception("Tipo de archivo irreconocible")
End If
End Function

'Devuelve el nombre de la variable y el nombre del tipo de la variable a partir de la línea de declaración
Private Sub ObtenerVariableYtipo(ByVal VisualBasic As Boolean, ByVal Linea As String, _
ByRef var As String, ByRef tipo As String)
var = ""
tipo = ""
'Obtengo el nombre de la variable y su tipo
Dim mc = Regex.Matches(Linea.Trim, If(VisualBasic, REGEX_Declaracion_VB, REGEX_Declaracion_CSHARP), _
RegexOptions.IgnoreCase Or RegexOptions.Singleline)

If mc.Count = 0 Then
Throw New Exception("La línea actual no es la declaración de una variable")
End If

For Each m As Match In mc
var = m.Groups("var").ToString
tipo = m.Groups("tipo").ToString
Next
End Sub

'Devuelve el texto convertido a propiedad
Private Function DevolverPropiedad(ByVal VisualBasic As Boolean, _
ByRef var As String, ByRef tipo As String) As String
If VisualBasic Then
Return (String.Format( _
"Private _{1} As {2}{3}" & _
"Public Property {1}() As {2}{3}" & _
"{0}Get{3}" & _
"{0}{0}Return _{1}{3}" & _
"{0}End Get{3}" & _
"{0}Set(ByVal value As {2}){3}" & _
"{0}{0}_{1} = value{3}" & _
"{0}End Set{3}" & _
"End Property{3}", _
vbTab, var, tipo, vbCrLf))
Else
Return (String.Format( _
"{0}{0}public {1} {2} {{ get; set; }}", vbTab, tipo, var))
End If
End Function

End Module
A continuación guardar y cerrar la ventana del editor de macros.

Ahora ya existe el método thepirat.campo2prop que a partir de la línea actual del editor del Visual Studio, convierte la declaración de un campo a una declaración de una propiedad con el mismo nombre.



Sólo falta vincular un acceso directo para poder utilizar la macro con una combinación de teclas.

Para esto, ir al menú Tools, Customize y luego click en el botón Keyboard... Buscar el método campo2prop como se muestra a continuación:


Presionar la combinación de teclas deseada en la parte indicada y luego darle click en Assign

Listo! Ahora desde cualquier ventana del editor de Visual Studio, se puede hacer esto:

Me paro en la línea de la declaración de un campo:


Presiono mi combinación de teclas y obtengo:



Datos personales