The next version of C# will feature a code refactoring engine built into the Visual Studio environment.
A term coined by Martin Fowler, code refactoring allows you to change the code structure without changing or affecting what the code itself actually does. For example, changing a variable name or packaging a few lines of code into a method are code refactoring. The main difference between C# 2.0 refactoring and a mere edit or find-and-replace is that you can harness the intelligence of the compiler to distinguish between code and comments, and so on. This article provides a preview of Visual C# 2.0 code refactoring, to be released with the next version of Visual Studio .NET, code-name Whidbey.
Why Refactoring?
Perhaps the single most important contributor to the long term maintainability of an application is how well laid-out and structured the code base is. Elements such as proper variable names, naming conventions, a consistent look and feel to statements, code format, and style enable readability by any developer, not just the one who wrote the code. Member variables encapsulation decouples clients from servers. Cohesive interface definitions enables interface reuse in other contexts. Allocation of interfaces to components is key to modular development and reuse. Eliminating blocks of repeated code by factoring it into a method increase quality because you only need to fix a defect in a single place.
Code refactoring allows you to change the code structure, without changing or affecting what the code itself actually does.
As a result, once you've laid out the initial code structure, many developers spend a lot of effort manually polishing it, pruning and grooming variables, methods, and interfaces. This manual process, while essential, is somewhat error-prone because it allows for mistakes, and it does not automatically enforce any standard. Tool-based refactoring can automate much of the manual process, making developers more productive and the resulting code base of higher quality. Tool-based refactoring relies heavily on the complier and its ability to discern and keep track of various symbols in the code. Refactoring may or may not change the public interface of a type?it is at the discretion of the developer whether the changes made should be limited to the internals of a single component or it should trigger a massive update of all the clients as well. In its simplest form, refactoring can rename types, variables, methods, or parameters, extract a method out of a code section (and insert a method call instead), extract an interface out of a set of methods the type already implements, encapsulate type members in properties, automate many formatting tasks and auto-expand common statements. This is what Visual C# 2.0 reformatting supports, and it is the subject of this article. Note that in this upcoming version, reformatting changes are limited to an assembly, and do not propagate to client assemblies, even in the same solution. More advanced forms or refactoring are also possible. For example, a refactoring engine could analyze your code for similar code sections that could be factored into a separate method, perhaps with different parameter values. Refactoring could enforce compliance with a coding standard and propagate changes across a set of interacting assemblies. No doubt, future versions of Visual C# .NET and other enterprise development tools from Microsoft will provide these and other advanced features. But for now, here are the refactoring features of Visual C# 2.0.
Renaming
You can use refactoring to intelligently rename a variable, parameter, method, or a type. Intelligently means that the refactoring tool will distinguish between literal names and words in comments, and different versions of an overloaded method. That is, when renaming a method, you will get the option to change the name of that particular version of the method (in its definition and all its call sites), or all overloaded versions of that method. You can invoke refactoring in two ways: you can select Refactor from the top level Visual Studio .NET menu, or you can Select from the pop-up context menu. For example, to rename the type Form1 to ClientForm, right-click anywhere in your code where type Form1 is present (in its definition or places it is being used), and select Rename... from the Refactor menu, as shown in Figure 1.
This will bring up the Rename dialog box shown in Figure 2 where you can preview the changes (always a good idea), and instruct the refactoring tools to rename inside comments and strings as well.
Supply ClientForm for the new form name and click OK. The Preview Changes dialog box shown in Figure 3 presents all the places in the assembly, across files, when the type Form1 is present. You can clear the checkbox before any occurrence where you do not want the renaming to take place. You can also double-click on each preview change to go to its code line. You can even keep the Preview Changes dialog box open, work on your code, and click the Refresh button (middle button in the dialog toolbar) to pick up the latest occurrences of the literal.
When you are satisfied with the list of changes, click the Start button to apply the rename. You can use refactoring to rename namespaces, types, variables, methods, properties, and parameters. Note that if you are used to naming the file after the type it contains (such as Form1.cs), after renaming a type you will need to manually rename the file as well.
Method Extraction
Refactoring method extraction lets you convert a section of in-line selected code into a method. The extraction removes the original code section, places it into a new private method, and injects a call to the new method instead of the extracted lines.
Tool-based refactoring relies heavily of the complier and its ability to discern and keep track of various symbols in the code.
When you generate the extracted method signature, the refactoring engine follows the following rules: (1) Any local variable that is defined before the selected code but is used in it becomes an input parameter. (2) Any such local value type variable that is changed by the selected code will be converted to a by-reference parameter (using the ref qualifier). (3) If the selected code does not use any member variables, it will be extracted to a static method.
For example, suppose in the following code snippet you highlight the third line and select Extract Method... from the Refactor menu:
int number1 = 1;
int number2 = 2;
int result = number1+number2;
Trace.WriteLine(result);
This will bring up the Extract Method dialog box shown in Figure 4. The bottom portion of the dialog shows the new method signature. Type the new method name and click OK.
The extracted method will be added as a new private static method, and the highlighted line will call it as in the following code snippet:
int number1 = 1;
int number2 = 2;
int result = Add(number1,number2);
Trace.WriteLine(result);
...
private static int Add(int number1,int number2)
{
int result = number1+number2;
return result;
}
Interface Extraction
My favorite refactoring feature is interface extraction, which creates an interface definition out of the public methods of a class or a struct. For example, consider the flowing Calculator class:
public abstract class Calculator
{
public int Add(int number1,int number2)
{
return number1+number2;
}
public int Subtract(int number1,int number2)
{
return number1-number2;
}
public virtual int Divide(int number1,int number2)
{
return number1+number2;
}
public abstract int Multiply(int number1,int number2);
}
To extract an interface out of the Calculator class, right-click anywhere inside the class definition and select Extract Interface... from the Refactor menu. This will bring up the Extract Interface dialog box, shown in Figure 5.
The dialog box will propose the interface name?the type's name prefixed with an I (the standard .NET naming convention for an interface). The interface will be extracted to a separate file (that will be automatically added to the project), and you can provide a file name in the dialog. Finally, all the public methods (or properties) of the type will be listed in the dialog, regardless of whether they are public, virtual, or abstract. Note that when you have a class hierarchy involved, the refactoring engine will only include public methods explicitly declared by the class or overridden methods. To include the suggested methods in the new interface definition, you must explicitly check the checkbox to the left of the method. After you click the OK button, the new interface will be in a new file, and the tool will add the interface derivation to the Calculator class, as shown here:
//In the file ICalculator.cs
interface ICalculator
{
int Add(int number1,int number2);
int Divide(int number1,int number2);
int Multiply(int number1,int number2);
int Subtruct(int number1,int number2);
}
//In the file Calculator.cs
public abstract class Calculator : ICalculator
{...}
You can even extract one interface from the definition of another, in which case the new interface will be placed in a new file, but the original interface definition will not change (such as inheriting from the new interface).
Interface extraction (in the current Alpha version) is not as smart as it should be. Specifically, if the type already implements as interface, that interface's members will be included in the Extract Interface dialog. The only workaround is to use explicit interface implementation (see my article, ".NET Interface-Based Programming," in the May 2002 issue of CoDe Magazine).
Field Encapsulation
This handy refactoring feature allows you to generate a property around a class member. You can use it to encapsulate a public field or to expose a private field as public property. For example, suppose you want to expose the m_Number member variable as a public property:
public class MyClass
{
int m_Number;
}
Place the cursor on m_Number and select Encapsulate Field... from the Refactor menu. This will bring up the Encapsulate Field dialog box shown in Figure 6.
EncapsulateField can recognize a commonly used member variable naming convention and generate the appropriate property name out of it. Meaning, if the member variable is prefixed with m_ or just _, the Encapsulate Field will omit that prefix when suggesting a property name. Of course, you can specify any property name you like. You can also specify the property's visibility (public, internal, protected internal, protected, private), and what should be done with external references: You can have the refactoring tool replace all references to the field (inside the type or outside) with references to the new property. Although the default reference update selection is set to External, I recommend always choosing All, because that will promote looser internal coupling in the type itself and that makes maintenance easier. Any business rule enforced by the property later on will apply automatically inside the type. You can choose if you want to review the changes to the references and apply the change. The result will be a public property wrapping the member:
public class MyClass
{
int m_Number;
public int Number
{
get
{
return m_Number;
}
set
{
m_Number = value;
}
}
}
You can use the field encapsulation capability to do just what its name implies. For example, instead of this public member variable:
public class MyClass
{
public int m_Number;
}
After using field encapsulation refactoring, you will end up with a public property called Number, and the public m_Number member will be converted to a private member:
public class MyClass
{
private int m_Number;
public int Number
{
get
{...}
set
{...}
}
}
Note that there is no refactoring support for generating an indexer or an iterator (another C# 2.0 feature). Unfortunately, Microsoft's design for encapsulation of an event field is poor. C# supports event accessors, which are property-like accessors encapsulating access to delegates. In my opinion, exposing member delegates in public should be explicitly forbidden by your C# coding standard. For example, instead of this definition:
public class MyPublisher
{
public event EventHandler m_MyEventHandler;
}
You should write:
public class MyPublisher
{
EventHandler m_MyEventHandler;
public event EventHandler MyEventHandler
{
add
{
m_MyEventHandler += value;
}
remove
{
m_MyEventHandler -= value;
}
}
}
Unfortunately, when you apply the field encapsulation refactoring selection to an event, it will generate the following invalid code:
//Invalid refactoring code
public class MyPublisher
{
private event EventHandler m_MyEventHandler;
public EventHandler MyEventHandler
{
get
{
return m_MyEventHandler;
}
set
{
m_MyEventHandler = value;
}
}
}
Be sure to always encapsulate your events, even without refactoring support.
Signature Change
Refactoring allows you to change the signature of a method by adding or removing parameters, and refactoring allows you to change the order of parameters. However, you cannot change the method returned type. You can change the signatures of methods, indexers, and constructors. For example, suppose you want to change the Add() method in this Calculator class to use double instead of int parameters:
public class Calculator
{
public int Add(int number1,int number2)
{
return number1+number2;
}
}
Right-click anywhere inside the method and select Change Method Signature... from the Refactor popup menu to bring up the Change Method Signature dialog box shown in Figure 7.
Use the dialog to change the order of parameters by moving parameters up or down, add or remove a parameter, and edit a parameter type and name.
Method extraction lets you convert a section of in-line selected code into a method.
For example, select the number1 parameter and click the Edit... button to bring up the Parameter dialog box. Change the parameter type to double. Note that the Parameter dialog will only let you change the type to one of the pre-defined C# types, such as int or string. Next, the Parameter dialog will warn you that the change you are about to do may render existing code invalid. Once you apply the signature change, you need to manually change the Add() method's returned type to double, as well as all its call sites. I find signature change to be of little practical value because it is usually faster to just change the signature manually using the code editor.
Surround With and Expansions
The last two refactoring features?surround with and expansions?are about code typing automation rather than code layout and structure.
Surround with generates a template with blank place holders for commonly used statements (such as foreach or exception handling) around a selected code section. For example, to automatically generate a foreach statement around a trace statement, highlight the statement, right-click, and select Refactor from the pop-up menu, then choose Surround With... and then select For Each, as shown in Figure 8.
It is important to understand that Kill() is not the same as Dispose(). Kill() handles execution flow such as application shutdown or timely termination of threads, whereas Dispose() caters to memory and resource management and disposing of other resources the WorkerThread class might hold. The only reason you might have Dispose() call Kill() is as a contingency in case the client developer forgets to do it.
This will insert a foreach statement were you need to fill in the blanks, by tabbing through them, as shown in Figure 9.
You can use the surround with statement to generate code for the following statements: If, Else, For, For Each, While, Do While, Region, and Try...Catch.
The Expand feature injects template code in-place. When you use Expand with control statements such as For Each, there is no need to surround existing code?it will simply expand a foreach statement where you need to fill in the blanks, similar to Figure 10. You can also use it to expand a multitude of code snippets from a static Main() method, (returning int or void, referred to as SIM and SMV respectively) to an enum definition. For example, to inject a reverse for statement, select Insert Expansion... from the Refactor menu. This will pop-up a scrollable list box, with the possible expansions. Select forr from it, as shown in Figure 10.
Table 1 lists the code statements available for extraction.
This will expand the code template shown in Figure 11, where you have to tab through the fields and fill in the blanks.
Table 1 shows the available code expansions in Visual C# 2.0.
Code expansions in the Visual Studio .NET Whidbey Alpha have a few glitches that I hope will be addressed before the product's release. When you expand an interface definition, the suggested interface name does not start with an I. Also, when you expand a lock statement, the tool injects a lock on the value true, which is not only wrong, it does not compile:
lock(true)
{
}
For now, you need to fix it manually, typically by locking on the this reference:
lock(this)
{
}
Code expansion allows developers to add their own code templates (called exstencils). You need to place an XML file in My Documents\Visual Studio Projects\VC#\Exstencil that provides Visual Studio .NET with the information for the custom exstencil.
Surround with and expansions are about code typing automation, rather than code layout and structure.
This is, of course, a very effective way to enable use of frameworks or coding standards. Look at the MSDN library for more information on how to compose custom exstencils.