ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


Refactoring in Whidbey

by Wei-Meng Lee
06/28/2004

Code refactoring is a term coined by Martin Fowler (www.refactoring.com, martinfowler.com). Even though the term may sound unfamiliar to you, many of you have actually used it. In a nutshell, code refactoring means restructuring your code so that the original intention of the code is preserved. For example, you may rename a variable so that it better reflects its usage. In this case, the entire application that uses this variable need to be updated with the new name. Another example of code refactoring is extracting a block of code and placing it into a function for more efficient code reuse. In either case, you would need to put in significant amount of effort to ensure you do not inadvertently inject errors into the modified code. In Visual Studio .NET 2005 (codenamed Whidbey), a code refactoring engine is built in. In this article, I will walk you through this new feature.

Extract Method

Very often, we write repetitive code within a function. Consider the following example:

private void Form1_Load(object sender, EventArgs e)
{
  int i, num, sum=0;
  for (i = 1; i <= num; i++) {
    sum += i;
  }
}

Here, I am summing up all the numbers from 1 to num; a very common operation. It would be better for me to package this block of code into a function. So I highlight the code (see Figure 1), right-click on it, and select Refactor -> Extract Method.


Figure 1. Extracting a block of code as a method

Supply a new name for your method (see Figure 2). You can also preview the default method signature that the refactoring engine has created for you. Click OK.


Figure 2. The Extract Method dialog

The block of statements is now encapsulated within a function:

private void Form1_Load(object sender, EventArgs e)
{
  int i, num, sum=0;
  i = Summation(num, ref sum);
}
private static int Summation(int num, ref int sum)
{
  int i;
  for (i = 1; i <= num; i++) {
    sum += i;
  }
  return i;
}

However, you still need to do some tweaking, as you obviously do not want to return the loop variant i; the variable sum should instead be returned.

The code you highlight will affect how the refactoring engine works. For example, if you include the variables declaration in the highlighting, a void function is created instead:

private static void Summation()
{
  int i, num, sum = 0;
  for (i = 1; i <= num; i++) {
    sum += i;
  }
}

While I find the method extraction feature useful, you should pay close attention to the new method signature and the return type. Often, some minor changes are needed in order to get what you want.

Consider another example:


int x, y, temp;
temp = x;
x = y;
y = temp;

If I include the variables declaration in the refactoring, a void function with no signature is generated:

private static void Swap()
{
  int x, y, temp;
  temp = x;
  x = y;
  y = temp;
}

However, if the variables declarations are not included, a method with three parameters is created:

private static void Swap(out int x, out int y, out int temp)
{
  temp = x;
  x = y;
  y = temp;
}

Here are the observations:

Note that C# supports two ways to pass in a parameter by reference: ref and out. The ref keyword requires that the variable be initialized before passing into the method. The out keyword does not require a variable to be initialized prior to calling the method; its main use is to pass a value out of the method.

Encapsulate Field

Consider the following partial declaration of a MyPoint class:

public class MyPoint
{
   public float x, y;

   public void MyPoint() {}
   public void MyPoint(float _x, float _y) {
      x = _x;
      y = _y;
   }
}

Here, the x and y variables represent the x and y coordinates of a point. It would be better to expose x and y as properties, rather than public members. So highlight and right-click x and choose Refactor -> Encapsulate Field... (see Figure 3).


Figure 3. Encapsulating a member variable as a property

You can then give a name to your property (see Figure 4).


Figure 4. Naming the property

You have the options to update either all external references or all references (including the one within the class). You can also preview the changes before they are made (see Figure 5).


Figure 5. Previewing the changes

Here is the result after applying the change to external references:

public class MyPoint
{
  public float x, y;
  public float X
  {
    get
    {
       return x;
    }
  
    set
    {
       x = value;
    }
  }
  public void MyPoint() {}
  public MyPoint(float _x, float _y) {
    x = _x;   // assigns through the member variable
    y = _y;
  }
}

MyPoint ptA = new MyPoint();
ptA.X = 5;   
ptA.y = 6;

And here is the result if you were to apply to all references:

public class MyPoint {
  public float x, y;
  public float X
  {
    get {
      return x;
    }
  
    set {
       x = value;
    }
  }
  public void MyPoint() {}
  public MyPoint(float _x, float _y) {
    X = _x;
    y = _y;
  }
}

MyPoint ptA = new MyPoint();
ptA.X = 5;   // assigns through the property
ptA.y = 6;

The difference lies in the assignment of the variable within the MyPoint() constructor (note the capitalization of X in either case). In general, it is always advisable to apply the update to all references. Also note that after the refactoring, you need to change the access modifier of x to private.

Extract Interface

You can also use the refactoring engine to extract an interface from a class definition. Consider the following example:

public class MyPoint {
  
  private float x, y;
  
  public float Y
  {
    get {
      return y;
    }
  
    set {
      y = value;
    }
  }
  
  public float X {
    get {
       return x;
    }
  
    set
    {
       x = value;
    }
  }
  
  public void MyPoint() {}

  public MyPoint(float _x, float _y) {
    X = _x;
    Y = _y;
  }

  public float getDistanceFromO() {
    // code implementations here
  }

  public float getDistanceFromPoint() {
    // code implementations here
  }
}

Right-click on any line within the class and select Refactor -> Extract Interface... (see Figure 6)


Figure 6. Extracting an interface from a class definition

The Extract Interface dialog will be shown. You can select the individual public members to form the interface (see Figure 7).


Figure 7. The Extract Interface dialog

The new interface will now be saved in a new .cs file:

using System;
namespace WindowsApplication1 {
  interface IMyPoint   {
    float getDistanceFromO();
    float getDistanceFromPoint();
    void MyPoint(float _x, float _y);
    float X { get; set; }
    float Y { get; set; }
  }
}

The original class definition now implements the newly created interface:

public class MyPoint : WindowsApplication1.IMyPoint
{    
  ...
  ...

Reorder and Remove Parameters

You can reorder the parameters in a method. Consider the following method from an earlier example:

private static int Summation(int num, ref int sum) {
  int i;
  for (i = 1; i <= num; i++)
    sum += i;
  return i;
}

Position your cursor within the signature and right-click and select Refactor -> Reorder Parameters... (see Figure 8).


Figure 8. Reordering a parameter list

You can then rearrange the order of the parameter list (see Figure 9). All statements that call the modified method will have their arguments order changed.


Figure 9. The Reorder Parameters dialog

Besides reordering parameters, you can also remove parameters (see Figure 8, Remove Parameters). The current implementation only allows one parameter to be removed at a time. I do not find this feature useful, as it is much more efficient removing the parameters manually.

Rename

Renaming variables is one common task that programmers do. However, if you are not careful, you may inadvertently rename the wrong variable (most people use the find-and-replace feature available in the IDE, which is susceptible to wrongly renaming variables). In C# refactoring, you can rename variables by selecting a variable and right-clicking on it. Select Refactoring -> Rename... (see Figure 10).


Figure 10. Renaming a variable

You will be prompted for a new name (see Figure 11).


Figure 11. The Rename dialog

As usual, you have the chance to preview the changes (see Figure 12):


Figure 12. Previewing the changes for variables' renaming

Promote Local Variable to Parameter

You can also promote a local variable into a parameter. Consider the following example:

private static int Summation(int num) {

  int i, start=0, sum=0;

  for (i = start; i <= num; i++) {
    sum += i;
  }

  return sum;
}

I want to promote the variable start into a parameter, so that callers of this function can initialize the start value. I select the variable start and right-click. I then select Refactor -> Promote Local Variable to Parameter (see Figure 13).


Figure 13. Promoting a variable to parameter

Note that the local variable to be promoted must be initialized, or else an error will occur. The promoted variable is now in the parameter list:

private static int Summation(int start, int num) {

  int i, sum = 0;

  for (i = start; i <= num; i++) {
    sum += i;
  }

  return sum;
}

Surround With

A very useful refactoring feature in C# 2.0 is Surround With. You can use this feature to enclose a block of code with the selected construct. Consider the following example:

private void Form1_Load(object sender, EventArgs e) {
   string FILE_NAME=@"c:\textfile.txt";
   System.IO.StreamReader sr = new System.IO.StreamReader(FILE_NAME);
   string line = sr.ReadLine();
   while (line != null) {
      Console.Write(line);
      line = sr.ReadLine();
   }
   sr.Close();
}

As performing I/O operations is one likely cause of system exceptions, it would be safer if you enclose the entire block of code within a Try-Catch block. Highlight the code that you want to enclose and right-click on it. Select Refactor -> Surround With... (See Figure 14).


Figure 14. Surrounding a block of code with a language construct

Select the required language construct. In this case, the Try-Catch construct (see Figure 15).


Figure 15. Using a Try-Catch block

You should see the modified code as shown in Figure 16.


Figure 16. The modified code with the Try-Catch block

Insert Expansion

Another refactoring feature in C# is Insert Expansion. This is VB.NET's equivalent of the Code Snippet that I wrote some time ago (www.ondotnet.com/pub/a/dotnet/2003/12/01/whidbeysnippets.html).

Insert Expansion allows you to quickly add a block of pre-built code. This is a good way to insert code blocks without needing to remember its exact syntax. To insert a code expansion, position the cursor and right-click. Select Refactor -> Insert Expansion... (see Figure 17).


Figure 17. Inserting a code expansion

You can then select the expansion snippet you want (see Figure 18).


Figure 18. Selecting the expansion snippet

Once the code snippet is inserted, you can customize the code by filling in the yellow boxes (see Figure 19).


Figure 19. Customize your code by changing the code in yellow

Just as in VB.NET, you can write your own custom expansion snippet. The snippets are XML files located in C:\Program Files\Microsoft Visual Studio 8\VC#\Expansions\1033\Expansions.

An expansion snippet looks like this:

   
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippet Format="1.0.0">
    <Header>
        <Title>if statement</Title>
        <Shortcut>if</Shortcut>
        <Description>Expansion snippet for if Statement</Description>
        <SnippetTypes>
            <SnippetType>Expansion</SnippetType>
            <SnippetType>SurroundsWith</SnippetType>
        </SnippetTypes>
    </Header>
    <Snippet>
        <Declarations>
            <Literal default="true">
                <ID>expression</ID>
                <ToolTip>Expression to evalute</ToolTip>
                <Default>true</Default>
            </Literal>
        </Declarations>
        <Code Language="csharp" Format="CData"><![CDATA[if ($expression$)
   {
      $selected$ $end$
   }]]>
   </Code>
    </Snippet>
</CodeSnippet>
   

Generate Method Stub

Another refactoring feature that is not listed when you perform a right-click is Generate Method Stub. You can see this feature when you click on the Refactor menu. This feature is useful when you want to call a function that has not been written yet. For example, I may have a function named getResult() that returns a string type. So I have:

private void Form1_Load(object sender, EventArgs e) {
	string result = getResult();
}

Position your cursor on the getResult() function and click on the Refactor -> Generate Method Stub menu item (see Figure 20).


Figure 20. Generating a method stub

It will automatically generate a method stub containing a Throw statement:

private string getResult()
{
  throw new NotImplementedException();
}

You can simply modify this stub for your use.

Summary

Code refactoring is one of new features in Visual Studio .NET 2005. It will certainly make the life of the C# developer much easier. Once again, Microsoft has made significant progress in the IDE, making .NET a very compelling framework for developing Windows applications.

Wei-Meng Lee (Microsoft MVP) http://weimenglee.blogspot.com is a technologist and founder of Developer Learning Solutions http://www.developerlearningsolutions.com, a technology company specializing in hands-on training on the latest Microsoft technologies.


Return to ONDotnet.com

Copyright © 2009 O'Reilly Media, Inc.