CodeDomProvider and Compiler Warning Levels

by Alex Meyer-Gleaves 1 September 2010 - 1:43 AM

When generating code using the CodeDomProvider you may noticed that by default you do not get the same warning messages in the CompilerResults that you receive when compiling in Visual Studio. For example, lets take the simple code below that has a static method with two unused variables.

public class Test
{
    static void testMethod()
    {
        int a;
        int b = 1;
    }
}

The code above when compiled in Visual Studio will result in two warning messages appearing in the Error List window.

Warnings in Error List

On the CodeDOM side we can confirm with a simple unit test that the default value for CompilerParameters.WarningLevel does not cause the two warnings to be returned when the same code is compiled with the CodeDomProvider.

[Test]
public void DefaultWarningLevel()
{
    StringBuilder snippet = new StringBuilder();
    snippet.AppendLine("public class Test");
    snippet.AppendLine("{");
    snippet.AppendLine("    static void testMethod()");
    snippet.AppendLine("    {");
    snippet.AppendLine("        int a;");
    snippet.AppendLine("        int b = 1;");
    snippet.AppendLine("    }");
    snippet.AppendLine("}");

    CodeDomProvider provider = CodeDomProvider.CreateProvider("C#");
    CompilerParameters parameters = new CompilerParameters {GenerateInMemory = true};

    CompilerResults results = provider.CompileAssemblyFromSource(parameters, snippet.ToString());
    Assert.That(results.Errors.Cast<CompilerError>().Any(r => r.IsWarning), Is.False);
}

If you check the Build tab in the Properties window for a new project in Visual Studio you can see that the default warning level is 4.

Default Warning Level

The MSDN documentation tells us that the valid warning levels are 0 through 4.

  • 0 - Turns off emission of all warning messages.
  • 1 - Displays severe warning messages.
  • 2 - Displays level 1 warnings plus certain, less-severe warnings, such as warnings about hiding class members.
  • 3 - Displays level 2 warnings plus certain, less-severe warnings, such as warnings about expressions that always evaluate to true or false.
  • 4 - Displays all level 3 warnings plus informational warnings. This is the default warning level at the command line.

Another simple unit test shows that setting CompilerParameters.WarningLevel to 4 will result in the same warnings as Visual Studio being returned.

[Test]
public void WarningLevelFour()
{
    StringBuilder snippet = new StringBuilder();
    snippet.AppendLine("public class Test");
    snippet.AppendLine("{");
    snippet.AppendLine("    static void testMethod()");
    snippet.AppendLine("    {");
    snippet.AppendLine("        int a;");
    snippet.AppendLine("        int b = 1;");
    snippet.AppendLine("    }");
    snippet.AppendLine("}");

    CodeDomProvider provider = CodeDomProvider.CreateProvider("C#");
    CompilerParameters parameters = new CompilerParameters
    {
        GenerateInMemory = true,
        WarningLevel = 4
    };

    CompilerResults results = provider.CompileAssemblyFromSource(parameters, snippet.ToString());
    Assert.That(results.Errors.Cast<CompilerError>().Count(r => r.IsWarning), Is.EqualTo(2));
    Assert.That(results.Errors[0].ErrorText, Is.StringContaining("The variable 'a' is declared but never used"));
    Assert.That(results.Errors[1].ErrorText, Is.StringContaining("The variable 'b' is assigned but its value is never used"));
}

We now have the same warnings that Visual Studio provides, even though the documentation for CompilerParameters.WarningLevel appears to be a little misleading.

Gets or sets the warning level at which the compiler aborts compilation.

The compilation will not be aborted for a warning unless the /warnaserror option is provided to the compiler. When compiling with the CodeDOM you use CompilerParameters.TreatWarningsAsErrors to abort the compilation for warnings. Needless to say Visual Studio has the same option available on the Build tab in the project Properties window.

Tags: ,

Categories: Microsoft .NET

Attaching the Debugger only in Debug

by Alex Meyer-Gleaves 16 June 2010 - 1:41 AM

I noticed an article on the Infinite Codex blog that demonstrates how to debug CLR Stored Procedures. The example uses a #if preprocessor directive to compile the debugging code only if the DEBUG symbol is defined. Personally, I find using the #if directive makes your code look rather ugly, and accidentally including code inside the directive that you did not intend to becomes a real possibility. My preferred solution is to use the ConditionalAttribute to refactor such code out into a separate method.

Applying ConditionalAttribute to a method indicates to compilers that a call to the method should not be compiled into Microsoft intermediate language (MSIL) unless the conditional compilation symbol that is associated withConditionalAttribute is defined.

Using this approach you can create a helper class that includes methods that should only be called when the DEBUG symbol is defined. When you compile with the RELEASE symbol defined all calls to the methods will simply be excluded from the generated MSIL.

public static class Debugging
{
    [Conditional("DEBUG")]
    public static void Break()
    {
        Debugger.Break();
    }
}

If you run the code below in Debug mode with the debugger attached, a breakpoint will occur immediately after the first Console.WriteLine method call.

class Program
{
    static void Main()
    {
        Console.WriteLine("Before the Debugging.Break() method.");
        Debugging.Break();
        Console.WriteLine("After the Debugging.Break() method.");
        Console.ReadLine();
    }
}

When the debugger is not attached and you compiled in Debug mode, the Visual Studio Just-In-Time Debugger dialog is presented for you to selected a debugger to attach with.

Visual Studio Just-In-Time Debugger Dialog

If you run in Release mode no breakpoint will be hit when the debugger is attached, and the Visual Studio Just-In-Time Debugger dialog will not be presented when you run without the debugger attached.

Tags: ,

Categories: Development Tools | Microsoft .NET

Compute any hash for any object in C#

by Alex Meyer-Gleaves 16 April 2009 - 1:36 AM

Approach

The .NET Framework already has many classes for cryptography in the System.Security.Cryptography namespace, so there is no need to worry about the hashing algorithms myself. The cryptography classes supplied by the .NET Framework expect to be given a byte array from which the hash can be computed. So, if I can create a hash from an array of bytes, and can convert any object into an array of bytes, then I will be able to calculate the hash for any object. Also, if I can select the CSP (Cryptographic Service Provider) to use for computing the hash, I can then “Compute any hash for any object”. Well, any hash algorithm supported by the .NET Framework at least.

Serialization

Starting with .NET Framework v3.5 SP1, turning any object into an array of bytes is actually very simple. This is because the DataContractSerializer was updated to allow for the serialization of POCO (Plain Old CLR Objects), and not just those marked with the Serializable or DataContract attributes. This means that we can take any object and serialize it into a MemoryStream, and then feed the array of bytes from the stream into the appropriate CSP to compute the hash.

Cryptographic Service Provider

As I mentioned before, there are many of these providers available in the .NET Framework, including providers for the common MD5 and SHA-1 hashing algorithms. The providers can be broken down as implementing one of two types of hash functions: those that require a key and those that don’t. Those that require a key are used for creating a HMAC (Hash Message Authentication Code), which is a combination of a hashing function and a secret key.

There are two abstract base classes defined for these two types of hashing functions: the HashAlgorithm and KeyedHashAlgorithm classes. The main difference is that the KeyedHashAlgorithm class has a Key property that can be used to set the secret key.

The HashAlgorithm and KeyedHashAlgorithm types.

Classes that derive from KeyedHashAlgorithm often have a constructor that excepts the key as a byte array, but this is for convenience only, and the property can be set at any time. This will make life easier when it comes to implementing support for the two types of hash functions.

Implementation

I decided to take advantage of the new Extension Methods language feature added to C# 3.0 to make the hashing methods available on all objects. The extension method for a normal hash function (one without a key) is below. You can see that the generic type parameter has a constraint indicating that the type argument must derive from HashAlgorithm and have a public parameterless constructor.

public static string GetHash<T>(this object instance) where T : HashAlgorithm, new()
{
    T cryptoServiceProvider = new T();
    return computeHash(instance, cryptoServiceProvider);
}

The extension method for a keyed hash function is similar but includes a byte array parameter for the key, and the type argument must instead derive from KeyedHashAlgorithm allowing the Key property to be set.

public static string GetKeyedHash<T>(this object instance, byte[] key) where T : KeyedHashAlgorithm, new()
{
    T cryptoServiceProvider = new T {Key = key};
    return computeHash(instance, cryptoServiceProvider);
}

Both of the extension methods call a private method to perform the actual serialization and the computation of the hash. This method creates an instance of the DataContractSerializer passing the type the extension method is being applied to into the constructor. A MemoryStream instance is created to hold the serialized representation of the object. The byte array of the stream is passed into the ComputeHash method of the HashAlgorithm instance to compute the hash. Finally, the hash is retrieved from the Hash property and converted to a base 64 string.

private static string computeHash<T>(object instance, T cryptoServiceProvider) where T : HashAlgorithm, new()
{
    DataContractSerializer serializer = new DataContractSerializer(instance.GetType());
    using (MemoryStream memoryStream = new MemoryStream())
    {
        serializer.WriteObject(memoryStream, instance);
        cryptoServiceProvider.ComputeHash(memoryStream.ToArray());
        return Convert.ToBase64String(cryptoServiceProvider.Hash);
    }
}

I also decided to add extension methods for the common MD5 and SHA-1 hash functions. These are simply helpers that call the GetHash<T> extension method providing the appropriate HashAlgorithm type for the type argument. The implementation of these helper methods are an example of how the generic extension methods are called.

public static string GetMD5Hash(this object instance)
{
    return instance.GetHash<MD5CryptoServiceProvider>();
}

public static string GetSHA1Hash(this object instance)
{
    return instance.GetHash<SHA1CryptoServiceProvider>();
}

Below is a simple example of using the GetKeyedHash<T> extension method.

byte[] key = new byte[] {1, 2, 3, 4};
DateTimeOffset now = DateTimeOffset.Now;
string hash = now.GetKeyedHash<HMACMD5>(key);
Console.WriteLine(hash);

For those like to copy and paste, here is the complete solution. I have also attached the C# code file to the end of this post. You will need to add a reference to the System.Runtime.Serialization assembly before compiling.

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Security.Cryptography;

namespace AlexMG.Shared
{
    /// <summary>
    ///     Extension methods applied to the <see cref="object"/> type.
    /// </summary>
    public static class ObjectExtensions
    {
        /// <summary>
        ///     Gets a hash of the current instance.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of the Cryptographic Service Provider to use.
        /// </typeparam>
        /// <param name="instance">
        ///     The instance being extended.
        /// </param>
        /// <returns>
        ///     A base 64 encoded string representation of the hash.
        /// </returns>
        public static string GetHash<T>(this object instance) where T : HashAlgorithm, new()
        {
            T cryptoServiceProvider = new T();
            return computeHash(instance, cryptoServiceProvider);
        }

        /// <summary>
        ///     Gets a key based hash of the current instance.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of the Cryptographic Service Provider to use.
        /// </typeparam>
        /// <param name="instance">
        ///     The instance being extended.
        /// </param>
        /// <param name="key">
        ///     The key passed into the Cryptographic Service Provider algorithm.
        /// </param>
        /// <returns>
        ///     A base 64 encoded string representation of the hash.
        /// </returns>
        public static string GetKeyedHash<T>(this object instance, byte[] key) where T : KeyedHashAlgorithm, new()
        {
            T cryptoServiceProvider = new T { Key = key };
            return computeHash(instance, cryptoServiceProvider);
        }

        /// <summary>
        ///     Gets a MD5 hash of the current instance.
        /// </summary>
        /// <param name="instance">
        ///     The instance being extended.
        /// </param>
        /// <returns>
        ///     A base 64 encoded string representation of the hash.
        /// </returns>
        public static string GetMD5Hash(this object instance)
        {
            return instance.GetHash<MD5CryptoServiceProvider>();
        }

        /// <summary>
        ///     Gets a SHA1 hash of the current instance.
        /// </summary>
        /// <param name="instance">
        ///     The instance being extended.
        /// </param>
        /// <returns>
        ///     A base 64 encoded string representation of the hash.
        /// </returns>
        public static string GetSHA1Hash(this object instance)
        {
            return instance.GetHash<SHA1CryptoServiceProvider>();
        }

        private static string computeHash<T>(object instance, T cryptoServiceProvider) where T : HashAlgorithm, new()
        {
            DataContractSerializer serializer = new DataContractSerializer(instance.GetType());
            using (MemoryStream memoryStream = new MemoryStream())
            {
                serializer.WriteObject(memoryStream, instance);
                cryptoServiceProvider.ComputeHash(memoryStream.ToArray());
                return Convert.ToBase64String(cryptoServiceProvider.Hash);
            }
        }
    }
}

Performance

The most expensive part of calculating the hash values is the serialization. This is not meant to be a replacement for the traditional testing of object equality and should be used carefully due to the serialization cost. I performed some basic testing of the NetDataContractSerializer and DataContractJsonSerializer as well. The DataContractJsonSerializer did not perform well when given large or complex object instances such a sizable DataSet. The NetDataContractSerializer performance was about the same as the DataContractSerializer.

ObjectExtensions.cs (3.30 kb)

Tags:

Categories: Garage Sale Code

About the author

Alex Meyer-Gleaves I'm a software developer living in Australia (that island like continent in the southern hemisphere). I love Microsoft .NET and C#. I hate early mornings, slow drivers and Lotus Notes.

Google Reader Clips

SpringWidgets
RSS Reader
This widget is the staple of our platform. Read all your feeds right here with thisone widget - Supported feeds are OPML, RSS, RDF, ATOM. Watch your favorite Podcastin the embedded Video Player on the Desktop or publish your own video playlist toyour site for others to view!

Recent Comments

Comment RSS

Links

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in  anyway.

© Copyright 2008