Introduction
Whenever I code I regularly find myself in a situation where I need to turn a
variable into a property. I know I should write all class attributes that are
publicly visible as properties but it is more hassle than just declaring a
variable public. To make my life easier I turned to Visual Studio's
Extensibility framework and built an add-in that automates this exercise for
me. Every VBA programmer knows that you can create routines within its office
suite to automate everyday tasks. The same holds for Visual Studio. You can
write macros using VBA and interact with the projects and files in your project.
But you do not have to use VBA - you can write add-ins in your .NET
language of choice. Being a right snob I write mine in C#.
Let's assume in my source file in visual studio I have a variable called Balance
public Currency Balance;
To convert it into a property I place the cursor on the variable and select
in the tool menu my menu item called 'Make Property'.
The result then should look as follows
public Currency Balance
{
get { return _Balance;}
set { _Balance = value;}
}
private Currency _Balance;
Creating the Add-in Project in VS.NET
In the New Project Dialog choose Other Project > Extensibility Projects > Visual Studio.Net Add-In
and name it MkProperty.
The New Project Wizard then leads you through a series of
questions such as what the add-in should be called in the tools menu. Make sure you check the 'create a tools menu item' option.
Once finished the wizard will create the appropriate class and set up the
OnConnect
routine which will ensure the add-in will be accessible through the
tools menu.
Using the code
Now comes the interesting part. When the menu Make Property is
called the Exec
method is invoked. In the Exec
method I call makeProperty()
From now on I will discuss what happens in bool makeProperty()
:
The first thing the method needs to do is to find where the cursor is and determine that it is located on a
variable.
private bool makeProperty()
{
TextSelection selection =
(TextSelection) applicationObject.ActiveWindow.Selection;
EditPoint Start = selection.TopPoint.CreateEditPoint();
CodeElement element = applicationObject.ActiveDocument.ProjectItem.
FileCodeModel.CodeElementFromPoint(Start,
vsCMElement.vsCMElementVariable);
....
This identifies a CodeElement
that represents whatever is under the cursor.
From there I establish that this is a variable declaration and cast it to
a CodeVariable
if (element.Kind == vsCMElement.vsCMElementVariable)
{
CodeVariable theVar = element as CodeVariable;
The variable named 'theVar' is a CodeVariable
which is one of the automation objects that makes life so easy.
At this point the method needs to do two things:
- Find out the name of the variable (which will become the name of the new
property)
- rename the variable (by adding an underscore in front of it)
Look at how easy the Extensibility framework makes this:
string propertyName = theVar.Name;
theVar.Name = "_" + propertyName;
Wasn't that easy??
The next step is to retrieve the accessibility (the new property needs to
have the same accessibility) and to change it to a private variable.
vsCMAccess propertyAccess = theVar.Access;
theVar.Access = vsCMAccess.vsCMAccessPrivate;
Now the method has to create the property. In preparation it has to get the type of the variable and
to determine the CodeElement
that
represents the owning class:
string varTypeName = theVar.Type.AsString;
CodeClass classElement = theVar.Parent as EnvDTE.CodeClass;
As you might expect, the CodeElement
of the owning class is the parent of the
CodeElement
representing the variable.
Now to create the property:
CodeProperty newProperty = classElement.AddProperty(
"A", "A", varTypeName,element,propertyAccess,element);
newProperty.Name = propertyName;
If you are starting to get confused by the parameters in this call, don't worry. Here we are getting to the rougher edges of the
Automation framework.
The first two parameters "A" and "A" are the getter and setter names for the property. As they are anonymous functions in
C# we don't have to worry about them.
The third parameter is the type which is the same type as the original variable.
The fourth parameter gives the position. As we want to insert the property right
next to the variable it is sufficient to simply
provide the CodeElement
representing the variable.
The last step is to actually specify the bodies of the get
and
set
methods. Unfortunately I have not been able to find a similarly
elegant approach that actually worked. Instead the following code simply pastes in the
raw code that is specified here.
string getExpression = "get { return " + var.Name + "; }";
EditPoint ep = newProperty.Getter.StartPoint.CreateEditPoint();
ep.ReplaceText(newProperty.Getter.EndPoint,
getExpression, vsEPReplaceTextOptionsAutoFormat);
While this is ugly compared to the previous bits it does redeem itself by providing the option to nicely format the code upon inserting it.
What is left now is to compile and deploy. VS.NET helps a lot, but sometimes
it gets itself into a knot. Make sure you read the instructions in the generated
code to help you out in case things go all out of shape.
When you build and run the project VS.NET will start another copy of itself.
You should find in the Tools menu an entry called MakeProperty
. Open a project
(not just a file, it has to be a project otherwise the CodeElements won't be
accessible) and place the cursor over a class variable. Select Tools->Make
Property and voila!
Points of Interest
When you create the project, the Wizard also creates an installation project.
If you get in a knot with VS.NET and it starts misbehaving then select the
install project, right click and select Uninstall in the popup menu.
Likewise,
before completing the project, fill in the properties in the install project
with a project name and your company name. This will affect the behaviour of the
created MSI file as it takes these two parameters to determine the install path.
At
the bottom of the source file you will find a routine called message(string msg)
.
This is a simple utility that outputs a message into the VS.NET window called
Output.
There
is a lot of information in the Visual Studio.NET HELP. You will find it in:
Visual Studio.NET
Developing with Visual Studio.NET
Manipulating the Development Environment
and
Reference
Next time I'll be writing an article on generating typed collections in VS.NET. Cheers