he 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 by first discussing refactoring in general, then providing an overview of Visual C# 2.0 refactoring by walking though the various options and discussing their use and benefits.
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.
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.
|Figure 1. The Refactor Pop-up Context Menu: Use this context menu to rename variables, parameters, methods, or types.
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.
|Figure 2. The Rename Dialog: This dialog lets you preview renaming changes before you apply them and set options for renaming the contents of comments and embedded strings.
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.
|Figure 3. The Preview Changes Dialog: You can select which change to apply, and even refresh the dialog to pick up new occurrences.
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.
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:
You can use the field encapsulation capability to do just what its name implies. For example, instead of this public member variable…
…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:
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:
You should write:
Unfortunately, when you apply the field encapsulation refactoring selection to an event, it will generate the following invalid code:
Be sure to always encapsulate your events, even without refactoring support.
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.
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
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 where 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 for from it, as shown in Figure 10.
Table 1 shows the available code expansions in Visual C# 2.0.
Table 1. Code Expansion Statements: The table lists the code expansion statements available to you when refactoring in C# 2.0.
Selecting an expansion type expands the code template shown in Figure 11, where you have to tab through the fields and fill in the blanks.