Hands-On Labs StyleCop and Code Analysis

08 December 2010 - .NET

This week I will be one of the speakers at BASTA On Tour in Munich. One of the topics I am going to speak about is the Managed Extensibility Framework (MEF). In this blog post I want to share my slides and summarize the hands-on labs that I am going to go through with the participants.

Hands-On Lab 1: StyleCop Documentation Rules

Prerequisites:

Lab step by step description:

  • Create a class library project StyleCopDemo.
  • Add the following class to the newly created project:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace styleCopDemo
{
 public class utils_Bucket<T>
 {
  public utils_Bucket()
  {
  }

  public utils_Bucket(IEnumerable<Tuple<string, T>> data)
  {
   foreach (var item in data) {
    this.data[item.Item1] = item.Item2;
   }
  }

  private Dictionary<string, T> data = new Dictionary<string, T>();

  public T this[string index]
  {
   get
   {
    if (data.ContainsKey(index))
    {
     return data[index];


    }
    else
     return default(T);
   }
  }

  public int GetLength()
  {
   return this.data.Keys.Count;
  }

  private string Dump()
  {
   return this.data.Aggregate<KeyValuePair<string, T>, StringBuilder>(
    new StringBuilder(),
    (agg, item) =>
    {
     if (agg.Length > 0)
     {
      agg.Append(", ");
     }
     agg.AppendFormat("{0}: {1}", item.Key, item.Value.ToString());
     return agg;
    }).ToString();
  }
  public override string ToString()
  {
   return this.Dump();
  }
 }
}
  • Launch StyleCop Settings (right-click on project, select StyleCop settings).
  • Enable all rules except Spacing Rules / SA1027
  • Build your project and make sure that there are no errors and no warning.
  • Run StyleCop on your project (right-click on project, select Run StyleCop). As you can see StyleCop generated a bunch of warnings.
  • Correct all warnings appropriately.
  • After that your file should look similar to the following implementation:
//-------------------------------------------------------
// <copyright file="Bucket.cs" company="Contoso Ltd.">
//     Copyright (c) Contoso Ltd. All rights reserved.
// </copyright>
//-------------------------------------------------------

namespace StyleCopDemo
{
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;

 /// <summary>
 /// Implements a bucket
 /// </summary>
 /// <typeparam name="T">Type of the elements in the bucket</typeparam>
 public class Bucket<T>
 {
  /// <summary>
  /// Internal helper to store data
  /// </summary>
  private Dictionary<string, T> data = new Dictionary<string, T>();

  /// <summary>
  /// Initializes a new instance of the Bucket class.
  /// </summary>
  public Bucket()
  {
  }

  /// <summary>
  /// Initializes a new instance of the Bucket class.
  /// </summary>
  /// <param name="data">The initial data.</param>
  public Bucket(IEnumerable<Tuple<string, T>> data)
  {
   foreach (var item in data) 
   {
    this.data[item.Item1] = item.Item2;
   }
  }

  /// <summary>
  /// Gets the element at the specified index.
  /// </summary>
  /// <param name="index">Index of the element to get.</param>
  /// <value>Element at the specified index.</value>
  public T this[string index]
  {
   get
   {
    if (this.data.ContainsKey(index))
    {
     return this.data[index];
    }
    else
    {
     return default(T);
    }
   }
  }

  /// <summary>
  /// Gets the length.
  /// </summary>
  /// <returns>Length of the dictionary</returns>
  public int GetLength()
  {
   return this.data.Keys.Count;
  }

  /// <summary>
  /// Returns a <see cref="System.String"/> that represents this instance.
  /// </summary>
  /// <returns>
  /// A <see cref="System.String"/> that represents this instance.
  /// </returns>
  public override string ToString()
  {
   return this.Dump();
  }

  /// <summary>
  /// Dumps this instance.
  /// </summary>
  /// <returns>String representation of the dictionary</returns>
  private string Dump()
  {
   return this.data.Aggregate<KeyValuePair<string, T>, StringBuilder>(
    new StringBuilder(),
    (agg, item) =>
    {
     if (agg.Length > 0)
     {
      agg.Append(", ");
     }

     agg.AppendFormat("{0}: {1}", item.Key, item.Value.ToString());
     return agg;
    }).ToString();
  }
 }
}

Hands-On Lab 2: StyleCop Build Integration

Prerequisites:

  • Visual Studio 2010
  • Download and install the latest version of the StyleCop from http://stylecop.codeplex.com/
    • Make sure that you have selected MSBuild integration files when installing StyleCop
  • Complete Hands-On Lab 1 (see above)

Lab step by step description:

  • Open the resulting solution from Hands-On Lab 1 (StyleCopDemo).
  • Unload the StyleCopDemo project (right-click on project in Solution Explorer, select Unload Project).
  • Edit StyleCopDemo project (right-click on project in Solution Explorer, select Edit StyleCopDemo.csproj).
  • Scroll to the end of the file. Find the line <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />.
  • Immediately after that line add the following line:
<Import Project="$(ProgramFiles)\MSBuild\Microsoft\StyleCop\v4.4\Microsoft.StyleCop.targets" />
  • Reload the StyleCopDemo project (right-click on project in Solution Explorer, select Reload Project).
  • Build your project to see that there are no errors and warnings.
  • Break a StyleCop rule (e.g. remove the documentation of a method).
  • Build your project.
    • StyleCop should have run automatically, you should see an appropriate warning.
  • Unload the StyleCopDemo project (right-click on project in Solution Explorer, select Unload Project).
  • Edit StyleCopDemo project (right-click on project in Solution Explorer, select Edit StyleCopDemo.csproj).
  • Find the first PropertyGroup in the project file and add the following setting:
<StyleCopTreatErrorsAsWarnings>false</StyleCopTreatErrorsAsWarnings>
  • Reload the StyleCopDemo project (right-click on project in Solution Explorer, select Reload Project).
  • Build your project.
    • The StyleCop warning should now be an error.
  • Suppress the warning/error using the SuppressMessage attribute:
[SuppressMessage("Microsoft.StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented", 
  Justification = "No time to write documentation...")]
public class Bucket<T>
{
  ...
}

Hands-On Lab 3: Code Analysis

Prerequisites:

  • Visual Studio 2010
  • Complete Hands-On Lab 1 (see above)

Lab step by step description:

  • Add a new class library project to solution from Hands-On Lab 1 (StyleCopDemo).
  • Add the following class to the newly created project:
namespace CodeAnalysisDemo
{
 using System;
 using System.Collections.Generic;
 using System.Data.SqlClient;
 using System.IO;

 public interface ISwiftItem<T>
 {
  string ItemName { get; }
  List<T> Values { get; }
 }

 public interface INamedItem
 {
  string ItemName { get; }
 }

 public class SwiftItem<T> : ISwiftItem<T>
 {
  public string ItemName
  {
   get;
   set;
  }

  public List<T> Values
  {
   get;
   set;
  }
 }

 public class SwiftFile
 {
  private Stream underlying_File;
  private string fileName;
  private object header;

  public SwiftFile(string fileName)
  {
   this.underlying_File = new FileStream(fileName, FileMode.Open);
   this.fileName = fileName;
   this.header = new object();
  }

  public List<Tuple<string, object>> Settings
  {
   get;
   set;
  }

  public void AddObject<T>(SwiftItem<T> newObj)
  {
   lock (this.header)
   {
    var header = string.Format("{0}: {1}", newObj.ItemName, newObj.Values.Count);

    foreach (var item in newObj.Values)
    {
     if (item is INamedItem)
     {
      var namedItem = item as INamedItem;

      // do something special with namedItem
     }
    }

    // do something with newObj
   }
  }

  public void CopyFile(string source_file_name)
  {
   using (var reader = new StreamReader(new FileStream(source_file_name, FileMode.Open)))
   {
    // TODO: copy file here
   }
  }

  public void WriteToDatabase(SqlConnection conn, string tenant)
  {
   var cmd = conn.CreateCommand();
   cmd.CommandText = string.Format("INSERT INTO Target ( Tenant, Data ) VALUES ( {0}, @Data )", tenant);
   
   // Build rest of the command and execute it
  }

  public void Close()
  {
   try
   {
    this.underlying_File.Close();
   }
   catch
   {
    Console.WriteLine("Error");
   }
  }
 }
}
  • Build your project and make sure that there are no error and no warnings.
  • Enable Code Analysis for the project and select rule set Microsoft All Rules.
    • You find the necessary options in the project's property window.
  • Build your project. As you can see code analysis shows you a bunch of warnings.
  • Try to correct all warnings appropriately. If you are not sure what a certain warning means or why it is important check the rule documentation in MSDN: http://msdn.microsoft.com/en-us/library/ee1hzekz.aspx
  • After that your file should look similar to the following implementation:
using System;

[assembly: CLSCompliant(true)]

namespace CodeAnalysisDemo
{
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Data;
 using System.Data.SqlClient;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;

 public interface ISwiftItem<T>
 {
  string ItemName { get; }
  IEnumerable<T> Values { get; }
 }

 public interface INamedItem
 {
  string ItemName { get; }
 }

 public class SwiftItem<T> : ISwiftItem<T>
 {
  public string ItemName
  {
   get;
   set;
  }

  public IEnumerable<T> Values
  {
   get;
   set;
  }
 }

 public class Setting
 {
  public string SettingName { get; set; }
  public object SettingValue { get; set; }
 }

 public class SettingCollection : Collection<Setting>
 {
 }

 public class SwiftFile : IDisposable
 {
  private Stream underlying_File;
  private object header;
  private bool disposed;

  public SwiftFile(string fileName)
  {
   this.underlying_File = new FileStream(fileName, FileMode.Open);
   this.header = new object();
   this.Settings = new SettingCollection();
  }

  public SettingCollection Settings
  {
   get;
   private set;
  }

  public void AddObject<T>(SwiftItem<T> newObj)
  {
   if (newObj != null)
   {
    lock (this.header)
    {
     foreach (var item in newObj.Values)
     {
      var namedItem = item as INamedItem;
      if (namedItem != null)
      {
       // do something special with namedItem
      }
     }

     // do something with newObj
    }
   }
  }

  [SuppressMessage("Microsoft.Performance", "CA1822", Justification = "Will reference 'this' later")]
  public void CopyFile(string sourceFileName)
  {
   var stream = new FileStream(sourceFileName, FileMode.Open);
   StreamReader reader;
   try
   {
    reader = new StreamReader(stream);
   }
   catch
   {
    stream.Dispose();
    throw;
   }

   try
   {
    // do something with reader
   }
   finally
   {
    reader.Dispose();
   }
  }

  [SuppressMessage("Microsoft.Performance", "CA1822", Justification = "Will reference 'this' later")]
  public void WriteToDatabase(SqlConnection conn, string tenant)
  {
   if (conn == null || (conn.State & ConnectionState.Open) == 0)
   {
    throw new ArgumentException("conn must not be null and must be open");
   }

   var cmd = conn.CreateCommand();
   cmd.CommandText = "INSERT INTO Target ( Tenant, Data ) VALUES ( @Tenant, @Data )";
   cmd.Parameters.Add("@Tenant", System.Data.SqlDbType.NVarChar, 100).Value = tenant;

   // Build rest of the command and execute it
  }

  public void Close()
  {
   try
   {
    this.underlying_File.Close();
   }
   catch
   {
    Console.WriteLine("Error");
    throw;
   }
  }

  private void Dispose(bool disposing)
  {
   if (!this.disposed)
   {
    if (disposing)
    {
     this.underlying_File.Dispose();
    }

    disposed = true;
   }
  }

  public void Dispose()
  {
   this.Dispose(true);
   GC.SuppressFinalize(this);
  }

  ~SwiftFile()
  {
   Dispose(false);
  }
 }
}