在编写c#代码过程中,我们经常需要做一些重复枯燥的工作。例如,编写DTO(数据访问对象),通常就是为一个类定义一系列的变量和属性。
有一些第三方的IDE辅助工具,可以为我们生成一些代码,减少工作量。例如,Assist X就是一款很值得推荐的工具,使用其提供的Encapsulate Field功能,可以很方便地将一个类地编写封装为属性。
我今天需要介绍的如何使用Visual Studio 2005 IDE中自带的宏实现类似的功能。
打开Visual Studio 2005 IDE,选择“工具” > "宏" > “宏 IDE”,选择“添加模块”。例如,我是在MyMacros项目中新增了一个EditorHelper模块,代码如下:
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics
Public Module EditorHelper
' 为一个参数封装一般属性访问器
Public Sub EncapsulateField()
Dim projectItem As ProjectItem = DTE.ActiveDocument.ProjectItem
Dim fileCodeModel As FileCodeModel = projectItem.FileCodeModel
' 得到当前选定的内容
Dim selectText As TextSelection = DTE.ActiveDocument.Selection
' 获取到当前光标的位置
Dim point As TextPoint = selectText.ActivePoint
Try
Dim codeElement As CodeElement = fileCodeModel.CodeElementFromPoint(point, vsCMElement.vsCMElementVariable)
If (codeElement Is Nothing ) Then
Return
End If
Debug.Assert(codeElement.Kind = vsCMElement.vsCMElementVariable)
Dim codeVar As CodeVariable = CType(codeElement, CodeVariable)
Dim fieldName As String = codeVar.Name
Dim codeClass As CodeClass = CType(codeVar.Parent, CodeClass)
AddPropertyToClass(codeClass, fieldName, codeVar.Type)
Catch ex As Exception
' 吃掉异常,不做处理或者提示
End Try
End Sub
Public Sub EncapsulateAllFields()
Dim projectItem As ProjectItem = DTE.ActiveDocument.ProjectItem
Dim fileCodeModel As FileCodeModel = projectItem.FileCodeModel
Try
' 得到当前选定的内容
Dim selectText As TextSelection = DTE.ActiveDocument.Selection
' 获取到当前光标的位置
Dim point As TextPoint = selectText.ActivePoint
Dim codeElement As CodeElement = fileCodeModel.CodeElementFromPoint(point, vsCMElement.vsCMElementClass)
Dim codeClass As CodeClass = CType(codeElement, CodeClass)
Dim i As Integer
For i = 1 To codeClass.Members.Count
' 如果属性已经定义,会抛出异常
' 在这里处理异常,即使新增的属性已经定义,也可以继续处理下面的代码
Try
Dim element As CodeElement = codeClass.Members.Item(i)
If (element.Kind = vsCMElement.vsCMElementVariable) Then
Dim codeVariable As CodeVariable = CType(element, CodeVariable)
If ( Not codeVariable.IsShared) Then ' 静态变量不需要增加属性
AddPropertyToClass(codeClass, codeVariable.Name, codeVariable.Type)
End If
End If
Catch ex As Exception
' 吃掉异常
End Try
Next
Catch ex As Exception
' 可能并没有选择有效的类定义,这时会抛出异常,忽略
End Try
End Sub
' 根据成员的名称的类型,在类对象中插入属性
Private Sub AddPropertyToClass(ByVal codeClass As CodeClass, ByVal fieldName As String , ByVal fieldType As Object )
' 生成属性的名称,规则是首先字母大写。如果变量的开头为“_”,移除
Dim propertyName As String = fieldName
If (propertyName.StartsWith( " _ " )) Then
propertyName = propertyName.TrimStart( " _ " c)
End If
propertyName = propertyName.Substring( 0 , 1 ).ToUpper() & propertyName.Substring( 1 )
' 创建属性对象
' -1表示代码插入到类的最下方
' vsCMAccess.vsCMAccessPublic表示为public
Dim codeProperty As CodeProperty = codeClass.AddProperty(propertyName, propertyName, fieldType, - 1 , vsCMAccess.vsCMAccessPublic)
' Getter
Dim getter As CodeFunction = codeProperty.Getter
Dim getterPoint As TextPoint = getter.GetStartPoint(vsCMPart.vsCMPartBody)
Dim getterEditPoint As EditPoint = getterPoint.CreateEditPoint()
getterEditPoint.Delete(getter.GetEndPoint(vsCMPart.vsCMPartBody))
getterEditPoint.Insert(vbCrLf) ' 插入回车符
getterEditPoint.LineUp()
getterEditPoint.Indent(, 4 ) ' 缩进4个位置
getterEditPoint.Insert( " return " & fieldName & " ; " )
' Setter
Dim setter As CodeFunction = codeProperty.Setter
Dim setterPoint As TextPoint = setter.GetStartPoint(vsCMPart.vsCMPartBody)
Dim setterEditPoint As EditPoint = setterPoint.CreateEditPoint()
setterEditPoint.Insert(vbCrLf) ' 插入回车符
setterEditPoint.LineUp()
setterEditPoint.Indent(, 4 ) ' 缩进4个位置
setterEditPoint.Insert(fieldName & " = value; " )
End Sub
End Module
Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics
Public Module EditorHelper
' 为一个参数封装一般属性访问器
Public Sub EncapsulateField()
Dim projectItem As ProjectItem = DTE.ActiveDocument.ProjectItem
Dim fileCodeModel As FileCodeModel = projectItem.FileCodeModel
' 得到当前选定的内容
Dim selectText As TextSelection = DTE.ActiveDocument.Selection
' 获取到当前光标的位置
Dim point As TextPoint = selectText.ActivePoint
Try
Dim codeElement As CodeElement = fileCodeModel.CodeElementFromPoint(point, vsCMElement.vsCMElementVariable)
If (codeElement Is Nothing ) Then
Return
End If
Debug.Assert(codeElement.Kind = vsCMElement.vsCMElementVariable)
Dim codeVar As CodeVariable = CType(codeElement, CodeVariable)
Dim fieldName As String = codeVar.Name
Dim codeClass As CodeClass = CType(codeVar.Parent, CodeClass)
AddPropertyToClass(codeClass, fieldName, codeVar.Type)
Catch ex As Exception
' 吃掉异常,不做处理或者提示
End Try
End Sub
Public Sub EncapsulateAllFields()
Dim projectItem As ProjectItem = DTE.ActiveDocument.ProjectItem
Dim fileCodeModel As FileCodeModel = projectItem.FileCodeModel
Try
' 得到当前选定的内容
Dim selectText As TextSelection = DTE.ActiveDocument.Selection
' 获取到当前光标的位置
Dim point As TextPoint = selectText.ActivePoint
Dim codeElement As CodeElement = fileCodeModel.CodeElementFromPoint(point, vsCMElement.vsCMElementClass)
Dim codeClass As CodeClass = CType(codeElement, CodeClass)
Dim i As Integer
For i = 1 To codeClass.Members.Count
' 如果属性已经定义,会抛出异常
' 在这里处理异常,即使新增的属性已经定义,也可以继续处理下面的代码
Try
Dim element As CodeElement = codeClass.Members.Item(i)
If (element.Kind = vsCMElement.vsCMElementVariable) Then
Dim codeVariable As CodeVariable = CType(element, CodeVariable)
If ( Not codeVariable.IsShared) Then ' 静态变量不需要增加属性
AddPropertyToClass(codeClass, codeVariable.Name, codeVariable.Type)
End If
End If
Catch ex As Exception
' 吃掉异常
End Try
Next
Catch ex As Exception
' 可能并没有选择有效的类定义,这时会抛出异常,忽略
End Try
End Sub
' 根据成员的名称的类型,在类对象中插入属性
Private Sub AddPropertyToClass(ByVal codeClass As CodeClass, ByVal fieldName As String , ByVal fieldType As Object )
' 生成属性的名称,规则是首先字母大写。如果变量的开头为“_”,移除
Dim propertyName As String = fieldName
If (propertyName.StartsWith( " _ " )) Then
propertyName = propertyName.TrimStart( " _ " c)
End If
propertyName = propertyName.Substring( 0 , 1 ).ToUpper() & propertyName.Substring( 1 )
' 创建属性对象
' -1表示代码插入到类的最下方
' vsCMAccess.vsCMAccessPublic表示为public
Dim codeProperty As CodeProperty = codeClass.AddProperty(propertyName, propertyName, fieldType, - 1 , vsCMAccess.vsCMAccessPublic)
' Getter
Dim getter As CodeFunction = codeProperty.Getter
Dim getterPoint As TextPoint = getter.GetStartPoint(vsCMPart.vsCMPartBody)
Dim getterEditPoint As EditPoint = getterPoint.CreateEditPoint()
getterEditPoint.Delete(getter.GetEndPoint(vsCMPart.vsCMPartBody))
getterEditPoint.Insert(vbCrLf) ' 插入回车符
getterEditPoint.LineUp()
getterEditPoint.Indent(, 4 ) ' 缩进4个位置
getterEditPoint.Insert( " return " & fieldName & " ; " )
' Setter
Dim setter As CodeFunction = codeProperty.Setter
Dim setterPoint As TextPoint = setter.GetStartPoint(vsCMPart.vsCMPartBody)
Dim setterEditPoint As EditPoint = setterPoint.CreateEditPoint()
setterEditPoint.Insert(vbCrLf) ' 插入回车符
setterEditPoint.LineUp()
setterEditPoint.Indent(, 4 ) ' 缩进4个位置
setterEditPoint.Insert(fieldName & " = value; " )
End Sub
End Module
我定义了两个Public方法:EncapsulateField和EncapsulateAllFields,分别用于为类的一个变量封装属性,或者为类中所有的变量(非静态)封装属性。
使用上面的宏的方法很简单,选择“工具”>“宏”>“宏资源管理器”就可以看到我们已经创建的宏方法,如下图所示:
假如你已经编写了这样一段代码:
using
System;
using System.Collections.Generic;
using System.Text;
namespace Demo
{
public class Person
{
private int _id;
private string _name;
private DateTime _birthDay;
}
}
using System.Collections.Generic;
using System.Text;
namespace Demo
{
public class Person
{
private int _id;
private string _name;
private DateTime _birthDay;
}
}
将光标移到“_name”变量上,然后双击“EncapsulateField”宏,就运行了该宏。运行后,你可以得到这样的代码:
using
System;
using System.Collections.Generic;
using System.Text;
namespace Demo
{
public class Person
{
private int _id;
private string _name;
private DateTime _birthDay;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
}
}
using System.Collections.Generic;
using System.Text;
namespace Demo
{
public class Person
{
private int _id;
private string _name;
private DateTime _birthDay;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
}
}
可以看到“EncapsulateField”宏已经为private string _name;创建了相应的属性。
EncapsulateAllFields 宏只需要将光标放在Person类的代码区域中,就可以正常执行。例如针对上面的代码,EncapsulateAllFields后可以为Person类 中的每一个变量都生成相应的属性。(注:上面的代码中Name属性已经有定义,所有试图再添加Name属性时会抛出异常,在 EncapsulateAllFields宏定义中,已经将该异常吃掉,所以,可以正确地为所有变量生成属性)。运行后的代码如下:
using
System;
using System.Collections.Generic;
using System.Text;
namespace Demo
{
public class Person
{
private int _id;
private string _name;
private DateTime _birthDay;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public int Id
{
get
{
return _id;
}
set
{
_id = value;
}
}
public System.DateTime BirthDay
{
get
{
return _birthDay;
}
set
{
_birthDay = value;
}
}
}
}
using System.Collections.Generic;
using System.Text;
namespace Demo
{
public class Person
{
private int _id;
private string _name;
private DateTime _birthDay;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public int Id
{
get
{
return _id;
}
set
{
_id = value;
}
}
public System.DateTime BirthDay
{
get
{
return _birthDay;
}
set
{
_birthDay = value;
}
}
}
}
在确认上面的宏可以正确运行的情况下,我们还可以为其定义快捷键,进一步提高我们的工作效率。
在Visual Studio 2005 IDE中选择“工具” > “选项”
在“选项”对话框中选择“环境”>“键盘”。
在【显示命令包含】中输入Encap...,可以帮你快速定位到相应的宏命令;
【新快捷键用于】选项中选择“文本编辑器”;
激活【按快捷键】输入框,选择你所希望的快捷键,例如我这里为EncapsulateField定义的快捷键为“Ctrl + `”,为EncapsulateAllFields定义的快捷键为“Ctrl + Shift + `”
点击分配按钮
你就可以在文本编辑器中尽情享用宏给你带来的方便。
补充在Visual Studio 2003中使用的宏:
Visual Studio 2003 IDE与Visual Studio 2005 IDE在处理上稍微有些不一致的地方,需要做如下的调整:
Imports System
Imports EnvDTE
Imports System.Diagnostics
Public Module EditorHelper
' 为一个参数封装一般属性访问器
Public Sub EncapsulateField()
Dim projectItem As ProjectItem = DTE.ActiveDocument.ProjectItem
Dim fileCodeModel As FileCodeModel = projectItem.FileCodeModel
' 得到当前选定的内容
Dim selectText As TextSelection = DTE.ActiveDocument.Selection
' 获取到当前光标的位置
Dim point As TextPoint = selectText.ActivePoint
Try
Dim codeElement As CodeElement = fileCodeModel.CodeElementFromPoint(point, vsCMElement.vsCMElementVariable)
If (codeElement Is Nothing ) Then
Return
End If
Debug.Assert(codeElement.Kind = vsCMElement.vsCMElementVariable)
Dim codeVar As CodeVariable = CType(codeElement, CodeVariable)
Dim fieldName As String = codeVar.Name
Dim codeClass As CodeClass = CType(codeVar.Parent, CodeClass)
AddPropertyToClass(codeClass, fieldName, codeVar.Type)
Catch ex As Exception
' 吃掉异常,不做处理或者提示
MsgBox (ex.Message)
End Try
End Sub
Public Sub EncapsulateAllFields()
Dim projectItem As ProjectItem = DTE.ActiveDocument.ProjectItem
Dim fileCodeModel As FileCodeModel = projectItem.FileCodeModel
Try
' 得到当前选定的内容
Dim selectText As TextSelection = DTE.ActiveDocument.Selection
' 获取到当前光标的位置
Dim point As TextPoint = selectText.ActivePoint
Dim codeElement As CodeElement = fileCodeModel.CodeElementFromPoint(point, vsCMElement.vsCMElementClass)
Dim codeClass As CodeClass = CType(codeElement, CodeClass)
Dim i As Integer
For i = 1 To codeClass.Members.Count
' 如果属性已经定义,会抛出异常
' 在这里处理异常,即使新增的属性已经定义,也可以继续处理下面的代码
Try
Dim element As CodeElement = codeClass.Members.Item(i)
If (element.Kind = vsCMElement.vsCMElementVariable) Then
Dim codeVariable As CodeVariable = CType(element, CodeVariable)
If ( Not codeVariable.IsShared) Then ' 静态变量不需要增加属性
AddPropertyToClass(codeClass, codeVariable.Name, codeVariable.Type)
End If
End If
Catch ex As Exception
' 吃掉异常
End Try
Next
Catch ex As Exception
' 可能并没有选择有效的类定义,这时会抛出异常,忽略
MsgBox (ex.Message)
End Try
End Sub
' 根据成员的名称的类型,在类对象中插入属性
Private Sub AddPropertyToClass(ByVal codeClass As CodeClass, ByVal fieldName As String , ByVal fieldType As Object )
' 生成属性的名称,规则是首先字母大写。如果变量的开头为“_”,移除
Dim propertyName As String = fieldName
If (propertyName.StartsWith( " _ " )) Then
propertyName = propertyName.TrimStart( " _ " c)
End If
propertyName = propertyName.Substring( 0 , 1 ).ToUpper() & propertyName.Substring( 1 )
' 创建属性对象
' -1表示代码插入到类的最下方
' vsCMAccess.vsCMAccessPublic表示为public
Dim codeProperty As CodeProperty = codeClass.AddProperty(propertyName, propertyName, fieldType, - 1 , vsCMAccess.vsCMAccessPublic)
' Getter
Dim getter As CodeFunction = codeProperty.Getter
Dim getterPoint As TextPoint = getter.GetStartPoint(vsCMPart.vsCMPartBody)
Dim getterEditPoint As EditPoint = getterPoint.CreateEditPoint()
getterEditPoint.Delete(getter.GetEndPoint(vsCMPart.vsCMPartBody))
getterEditPoint.Insert( " get{ return " & fieldName & " ; } " )
' Setter
Dim setter As CodeFunction = codeProperty.Setter
Dim setterPoint As TextPoint = setter.GetStartPoint(vsCMPart.vsCMPartBody)
Dim setterEditPoint As EditPoint = setterPoint.CreateEditPoint()
setterEditPoint.Delete(setter.GetEndPoint(vsCMPart.vsCMPartBody))
setterEditPoint.Insert( " set{ " & fieldName & " = value; } " )
End Sub
End Module
Imports EnvDTE
Imports System.Diagnostics
Public Module EditorHelper
' 为一个参数封装一般属性访问器
Public Sub EncapsulateField()
Dim projectItem As ProjectItem = DTE.ActiveDocument.ProjectItem
Dim fileCodeModel As FileCodeModel = projectItem.FileCodeModel
' 得到当前选定的内容
Dim selectText As TextSelection = DTE.ActiveDocument.Selection
' 获取到当前光标的位置
Dim point As TextPoint = selectText.ActivePoint
Try
Dim codeElement As CodeElement = fileCodeModel.CodeElementFromPoint(point, vsCMElement.vsCMElementVariable)
If (codeElement Is Nothing ) Then
Return
End If
Debug.Assert(codeElement.Kind = vsCMElement.vsCMElementVariable)
Dim codeVar As CodeVariable = CType(codeElement, CodeVariable)
Dim fieldName As String = codeVar.Name
Dim codeClass As CodeClass = CType(codeVar.Parent, CodeClass)
AddPropertyToClass(codeClass, fieldName, codeVar.Type)
Catch ex As Exception
' 吃掉异常,不做处理或者提示
MsgBox (ex.Message)
End Try
End Sub
Public Sub EncapsulateAllFields()
Dim projectItem As ProjectItem = DTE.ActiveDocument.ProjectItem
Dim fileCodeModel As FileCodeModel = projectItem.FileCodeModel
Try
' 得到当前选定的内容
Dim selectText As TextSelection = DTE.ActiveDocument.Selection
' 获取到当前光标的位置
Dim point As TextPoint = selectText.ActivePoint
Dim codeElement As CodeElement = fileCodeModel.CodeElementFromPoint(point, vsCMElement.vsCMElementClass)
Dim codeClass As CodeClass = CType(codeElement, CodeClass)
Dim i As Integer
For i = 1 To codeClass.Members.Count
' 如果属性已经定义,会抛出异常
' 在这里处理异常,即使新增的属性已经定义,也可以继续处理下面的代码
Try
Dim element As CodeElement = codeClass.Members.Item(i)
If (element.Kind = vsCMElement.vsCMElementVariable) Then
Dim codeVariable As CodeVariable = CType(element, CodeVariable)
If ( Not codeVariable.IsShared) Then ' 静态变量不需要增加属性
AddPropertyToClass(codeClass, codeVariable.Name, codeVariable.Type)
End If
End If
Catch ex As Exception
' 吃掉异常
End Try
Next
Catch ex As Exception
' 可能并没有选择有效的类定义,这时会抛出异常,忽略
MsgBox (ex.Message)
End Try
End Sub
' 根据成员的名称的类型,在类对象中插入属性
Private Sub AddPropertyToClass(ByVal codeClass As CodeClass, ByVal fieldName As String , ByVal fieldType As Object )
' 生成属性的名称,规则是首先字母大写。如果变量的开头为“_”,移除
Dim propertyName As String = fieldName
If (propertyName.StartsWith( " _ " )) Then
propertyName = propertyName.TrimStart( " _ " c)
End If
propertyName = propertyName.Substring( 0 , 1 ).ToUpper() & propertyName.Substring( 1 )
' 创建属性对象
' -1表示代码插入到类的最下方
' vsCMAccess.vsCMAccessPublic表示为public
Dim codeProperty As CodeProperty = codeClass.AddProperty(propertyName, propertyName, fieldType, - 1 , vsCMAccess.vsCMAccessPublic)
' Getter
Dim getter As CodeFunction = codeProperty.Getter
Dim getterPoint As TextPoint = getter.GetStartPoint(vsCMPart.vsCMPartBody)
Dim getterEditPoint As EditPoint = getterPoint.CreateEditPoint()
getterEditPoint.Delete(getter.GetEndPoint(vsCMPart.vsCMPartBody))
getterEditPoint.Insert( " get{ return " & fieldName & " ; } " )
' Setter
Dim setter As CodeFunction = codeProperty.Setter
Dim setterPoint As TextPoint = setter.GetStartPoint(vsCMPart.vsCMPartBody)
Dim setterEditPoint As EditPoint = setterPoint.CreateEditPoint()
setterEditPoint.Delete(setter.GetEndPoint(vsCMPart.vsCMPartBody))
setterEditPoint.Insert( " set{ " & fieldName & " = value; } " )
End Sub
End Module
另 外,在2003中使用EncapsulateAllFields的宏时,也需要注意一点:在使用时,光标的应该停留在类定义的空白位置,否则使用Dim codeClass As CodeClass = CType(codeElement, CodeClass)方法不能正确获取到类对象(例如,如果光标在构造函数里,获取到的是构造函数对象,这点与2005还是有所区别的)。
参考:
《用Visual Studio 2005创建宏代码生成器》:转载太多,已经不知道原出处