Go4Expert

Go4Expert (http://www.go4expert.com/)
-   C# (http://www.go4expert.com/articles/c-sharp-tutorials/)
-   -   Code Diagnostics with Code Contracts in C# and Conditional Compilation (http://www.go4expert.com/articles/code-diagnostics-code-contracts-c-sharp-t30061/)

shabbir 14Apr2014 10:44

Code Diagnostics with Code Contracts in C# and Conditional Compilation
 
.NET Framework comes with a set of debugging tools. Particularly if you are using and advanced IDE such as Visual Studio, you can use debugger and other diagnostic tools available in the IDE. This debugging, however, is only applicable during the development of the application. Once the application is developed and shipped and then if an error occurs, it is not easy to diagnose and remove those errors via IDE debuggers. To handle such runtime application errors, special information is added with the code which can be later on used to diagnose and treat the error and bugs once the application is deployed. This chapter throws light on those techniques. Let us start this article with conditional compilation.
  1. Conditional Compilation
  2. Debug & Trace
  3. What are TraceListeners?
  4. Code Contracts

Conditional Compilation



Conditional compilation, as the name suggests, refers to the compilation of certain area of code based on some condition. Conditional compilation is done via preprocessor directive #. The idea behind preprocessor directives is that they are compiled before the main compilation takes place. There are four preprocessor directives that are most commonly used for conditional compilation. They are #if, #else, #endif, #elif.

Basically advantage of conditional compilation is that you can compile source codes for different platforms, also you can compile your code for testing purposes or for deployment purposes. The idea is that when a compiler reaches #if statement, it checks whether a corresponding symbol has been defined or not. If symbol is not defined, that piece of code is ignored otherwise that piece of code is executed. The symbol is defined at the top of the file via #define followed by symbol name which is capital by convention. To define symbol for a complete assembly, a compilation switch is used. Have a look at the first example to further understand this concept.

Example1
Code:

#define TESTING
#define DEBUG

using System;

namespace CSharpTutorial
{
    class Program
    {
        public static void Main()
        {
#if TESTING
            Console.WriteLine("The program is running in testing mode");
            Console.WriteLine("testing ...");
#else
            console.WriteLine("Program is running in actual mode");
#endif

#if TESTING && !DEBUG
            Console.WriteLine("Testing but not debug");
#else
            Console.WriteLine("Testing and debug mode");
#endif
            Console.ReadLine();
        }
    }
}

Look at the beginning of the code; here we have specified two symbols via #define. Remember, the symbols would always be at the top of the file, even before you import namespace. We have two symbols TESTING & DEGBUG.

Now come inside the main method, here we have first used preprocessor directive #if followed by symbol TESTING which means that if TESTING symbol has been defined in the file, compile this section of code. #else works as typical else statement which says that if TESTING is not defined in the file, execute the code after #else.

You can also use logical operators with preprocessor directives as we did next in our code. After #if we said that if TESTING symbol has been defined AND DEBUG has NOT been defined, then execute the following code. But since, we have both defined in our file, the code after else would be executed. You can see how we used the ‘&&’ and ‘!’ operators.

The output of the code in Example1 is as follows:

Output1

http://imgs.g4estatic.com/c-sharp/as...ts/output1.png

Conditional Attribute

In Example1, we described that how we can use preprocessor directives to decide which piece of code should execute and which should not. We can do same with methods. For example, we can write the code in which a method is called only if a certain symbol has been defined in the file.

One way to do this is to wrap all the calls to the method in #if preprocessor directive. This is shown in Example2.

Example2

Code:

#define TESTING
#define DEBUG

using System;

namespace CSharpTutorial
{
    class Program
    {
        static void Print(string str)
        {
            Console.WriteLine(str);
        }

        public static void Main()
        { 
#if !TESTING
            Print("Not testing mode ...");
#else
            Print("In testing mode ..."); 
#endif
            Console.ReadLine();
        }
    }
}

Notice that here we have a method Print which prints the string passed to it. In console method, we are calling this method inside the preprocessor directive. This method would be called on when TESTING symbol is present at the top. The output of the code in Example2 is as follows:

Output2

http://imgs.g4estatic.com/c-sharp/as...ts/output2.png

This technique works perfectly fine but the code us cumbersome to write. If you have multiple calls to the Print method in your code you will have to enclose every call within these preprocessor directives. However, there is another more efficient way to do this and that is why Conditional attribute. Our next example demonstrates that how conditional attribute can be used in such scenarios.

Example3

Code:

#define TESTING
#define DEBUG

using System;
using System.Diagnostics;

namespace CSharpTutorial
{
    class Program
    {
        [Conditional("TESTING")]
        static void Print(string str)
        {
            Console.WriteLine(str);
        }

        public static void Main()
        {
            Print("conditional");
            Print("conditional2");
            Console.ReadLine();
        }
    }
}

Have a look at the third example. First of all you need to include System.Diagnostics namespace to your program because this is the namespace where Conditional attribute is defined. Next, you want that Print method only executes if symbol TESTING is defined in the file. You can do this by simply appending [Condition (Symbol Name)] before the method where Symbol Name is a string which represents the name of the symbol. In Example3, we did this in following lines:
Code:

[Conditional("TESTING")]
static void Print(string str)
{
    Console.WriteLine(str);
}

Now, you can call Print method as many times as you like it will execute only if the symbol TESTING has been defined in the beginning of the file. We called Print function twice in our main method and would twice display the string that we pass it since TESTING symbol has been defined in the file. However, if you replace TESTING in Conditional attribute with any symbol that is not contained by the file, you would see that Print function will not execute no matter how many times you call it. The output of the code in Example3 is as follows:

Output3

http://imgs.g4estatic.com/c-sharp/as...ts/output3.png

You can see that how helpful Conditional operators can be while you have multiple calls to a method that you want to execute conditionally. You should not that Conditional attribute is merely an instruction to the compiler during compile time and is totally ignored at runtime.

Debug & Trace



Debug & Trace classes are used to implement basic assertion and logging into the application. Both of these classes are static and their major difference lays in their usage with different build types. Debug class is used mostly with the debug build whereas the Trace class works with both the trace and debug build.

An important point here is that you have to use a Conditional [(“DEBUG”)] with all the methods of the Debug class and Conditional [(“TRACE”)] with all the methods of the Trace class. This means that in order for the calls to the Debug and Trace class, execute, you will have to include DEBUG and TRACE symbols in your file or assembly respectively. However, luckily for us, Visual Studio does this task for us and incorporate DEBUG symbol and TRACE symbol in debug configuration and release configuration for every project, respectively.

The common methods contained by both Debug and Trace classes are Write, WriteLine, and WriteIf methods. The output of these methods is displayed on the debugger’s output window. Have a look at the next example to see how these methods actually work.

Example4

Code:

#define TESTING
#define DEBUG

using System;
using System.Diagnostics;
namespace CSharpTutorial
{
    class Program
    {
        public static void Main()
        {
            int a = 10;
            int b = 5;
            Debug.WriteLine(a + b);
            Trace.WriteLine(a - b);
            Debug.WriteLine("new line");
            Trace.WriteIf(a > b, "A is greater than B ");

            Console.ReadLine();
        }
    }
}

If you compile the above code and check the output window of your Visual Studio’s debugger you would see that this data would be written there. Look at the following screen shot of my debugger’s output window:

http://imgs.g4estatic.com/c-sharp/as...cts/debug1.png

You can see that the sum, subtraction and the values that we wrote using Debug and Trace methods have been displayed in the output window.

Trace class contains some additional methods such as TraceWarning, TraceInformation and TraceError which register themselves to the TraceListeners that are active. (TraceListeners have been explained in the later sections)

Understanding Fail & Assert methods

Debug and Trace classes contain TraceListeners and Listeners Collection respectively. When a Fail is called the information is sent to these listeners which in turn process the information and write them to the debug output and also display a message box with an error. This dialog box asks you to abort the abort, ignore or retry. If you select retry, it lets you attach the debugger which can be used to diagnose the problem.

Assert is similar to Fail and accepts two arguments. The first argument is of type bool and the second argument is the error message that you want to display in case if first argument is false. Think Assert method int terms of real life assertions. For example, we assert that pizza delivery should take thirty minutes. If this assertion proves correct and pizza is delivered in 30 minutes we are happy with the pizza delivery service but if pizza deiver takes more than 30 minutes we say that “sorry, we can’t wait this much” you have to improve this, this is a defect in your system.

Both Fail & Assert methods have been explained in our next example.

Example5

Code:

using System;
using System.Diagnostics;

namespace CSharpTutorial
{
    class Program
    {
        public static void Main()
        {
            Trace.Fail("file doesnt exist");
            int a = 10;
            int b = 5;
            bool result = a < b;
            Debug.Assert(result, "a is not greater than b");

            Console.ReadLine();
        }
    }
}

The code in Example5 is extremely straight-forward. We have first implemented the Fail method of the Trace class; this would display a Dialog box displaying the complete error message starting with the string you passed. Fail method can also be called from the Debug class. We then have two variables ‘a’ & ‘b’ with values 10 and 5 respectively. Next have a bool variable ‘result’ which contains the result of the condition ‘a’ is less than ‘b’ which would evaluate to false. We called Assert method of the Debug class and passed it the bool variable ‘result’ as the first parameter and the error message we want to display as the second message. The output would contain two error messages, first for the Fail method and second for the Assert method as shown below:

http://imgs.g4estatic.com/c-sharp/as...ail-assert.png

The dialogue on right is displayed by the Fail method in Example5 and the dialog on the left is displayed by the Assert method. You can see the error messages we passed at the start of both dialogs.

What are TraceListeners?



We have used the term TraceListeners many times in the last section. TraceListeners are used to process the information passed by the Fail, Assert and Write method of Debug and Trace classes. Basically Debug & Trace classes contain Listeners property which holds a collection of different types of static TraceListener instances. By default the Listeners collection contain only one listener DefaultTraceListener which writes the information passed by the Write, Fail and Assert method to the debugger’s output window and it also displays the dialog box as shown in the last example. You can remove the default listener if you want and add you custom listeners by inheriting from the TraceListener class or you can also use any of the built in listener classes. In our next example, we will demonstrate that how you can remove the default listener and add some of the other predefined listeners to the program. Have a look at our 6th example.

Example6

Code:

using System;
using System.Diagnostics;

namespace CSharpTutorial
{
    class Program
    {
        public static void Main()
        {
            Trace.Listeners.Clear();

            System.IO.TextWriter consollistener = Console.Out;
            Trace.Listeners.Add(new TextWriterTraceListener(consollistener));
            Trace.Listeners.Add(new TextWriterTraceListener(@"D:\mytrace.txt"));

            Trace.AutoFlush = true;

            Trace.Fail("file doesnt exist");
            int a = 10;
            int b = 5;
            bool result = a < b;
            Debug.Assert(result, "a is not greater than b");

            Console.ReadLine();
        }
    }
}

Here we have added two built-in trace listeners. First we cleared the Listeners collection by calling Clear. We then obtained a stream to the console output and then passed it to the TextWriterTraceListener object which is in turn passed to the Add method of the Listeners property. The Add method of the listener property is used to add new trace listeners to the trace listener’s collection. We again added another TextWriterTraceListener that writes the file. We passed the name of the file to this class this time. Finally we called AutoFlush on the trace that is used to flush the buffer memory occupied by the trace.

Next, we have same information that we had in Example5 but in that case, the result of the Fail and Assert methods were written to the debuggers output and displayed in the dialog box, since there was only one DefaulTraceListener. Now we have removed the default listener and have added two of our own. So this time when you run the code in Example6, you would see that the output of the Fail and Assert method would be displayed on the console and written in ‘mytrace.txt’ file located in your ‘D’ directory, since these are the two active listeners in the listener collection. The output of the code is as follows:

Output6

http://imgs.g4estatic.com/c-sharp/as...ts/output6.png

Also, you can go the ‘D’ directory and open “mytrace.txt” file; you would see the above output in that file too.

Code Contracts



In the last section, we explained that how Assert method of both Debug and Trace classes can be used to check whether a certain condition is met in the program or not. In Example6, we make an assertion that the result of the condition ‘a’ is less than ‘b’ should evaluate to true. This is basically our assertion that if this condition is satisfied, program should execute normally otherwise it should invoke the debugger and should throw an exception in case of Debug and Trace releases respectively.

However, a new more robust concept was introduced in the .NET Framework 4 which is commonly referred to as code contracts. Code contract is basically a contract between the caller method and the called method which further substantiates the assertion principle implemented by calling Assert method or by throwing custom exceptions. There are two integral parts of this code contract: A precondition and a postcondition. Meeting precondition is the responsibility of the calling method and meeting postcondition is the responsibility of the called method. This would be explained with the help of our next example. Have a look at the 7th example of this article.

Example7

Code:

using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Collections.Generic;

namespace CSharpTutorial
{
    class Program
    {
        public static bool RemoveItem(IList<int> numbers, int num )
        {
            Contract.Requires(numbers != null);
            Contract.Requires(!numbers.IsReadOnly);
            Contract.Ensures(!numbers.Contains(num));

            if (numbers.Contains(num))
            {
                numbers.Remove(num);             
                return true;
            }
            else return false;
        }

        public static void Main()
        {
            List<int> numcoll = new List<int> { 1, 3, 5, 7, 9 };

            if (RemoveItem(numcoll, 5))
            {
                Console.WriteLine("Remove successful");
            }
            else
                Console.WriteLine("Remove not successful");

            Console.ReadLine();
        }
    }
}

Closely look at the code in Example7. First of all we have to import System.Diagnostics.Contracts namespace. Then we have a RemoveItem method which accepts two parameters: a List collection of type integer which we have named ‘numbers’ and an integer variable which we named ‘num’. The first three lines inside the RemoveItem methods are basically contract lines. Have a look at them again:
Code:

Contract.Requires(numbers != null);
Contract.Requires(!numbers.IsReadOnly);
Contract.Ensures(!numbers.Contains(num));

Preconditions

Here, first two lines are preconditions. They are specified by calling Requires method of the Contract class. Inside the Requires method, we have to specify the condition that must be met by the calling function before the function execution starts. The first precondition we have is that the List collection ‘numbers’ which is being passed to this method should not be null. Second precondition is that the ‘numbers’ collection should not be readable because in that case, item would not be removed.

Postconditions

Post conditions are specified by calling Ensures method of Contract class. Inside the Ensures parameter we have to specify the condition which must be met when this function completes execution. In our example, we specified that the ‘numbers’ collection should not contain the ‘num’ item, when this method completes execution.

An important point here about preconditions and postconditions is that both are static methods. Both of them have to be defined at the start of any method because these are the conditions that have to be followed while writing actual code. Major difference between preconditions and postconditions lay in their verification time. Preconditions are verified before function execution starts, and postconditions are verified when function execution completes.

Coming back towards our 7th example; here in the main method we created a List collection which we named numcoll and we initialized it with 5 numbers. We passed it to RemoveItem method along with a number which is contained by numcoll. The numcoll method would return true. The output of the code in Example7 would be as follows:

Output7

http://imgs.g4estatic.com/c-sharp/as...s/]output7.png

An interesting thing to note here is that the methods in Contract class are defined with a Conditional attribute which is [Conditional (“CONTRACTS_FULL”)]. It means that unless you define the “CONTRACT_FULL” symbol in your program, you cannot take advantage of Contract methods as they would not be executed. However, luckily enough for us, if you go the project properties in the visual studio and enable the contract checking checkbox in the Code Contracts tab, this CONTRACTS_FULL is automatically added to the assembly. But for Code Contract tab to appear in Visual Studio, you will first have to download and install Contracts tool. This tool is available at Microsoft DevLabs site at this link.

Conditional compilation, coupled with code contracts can save you lots of time while diagnosing your application’s error during debugging as well as when application is deployed. In this article, we explained most fundamental concepts of conditional compilation and code contracts. I would advise you to implement the examples in this article yourself and also modify their code and see the impact in the output. If you need any help regarding the discussed topic, leave a message in the comments sections and I will get back as soon as possible. Happy Coding!


All times are GMT +5.5. The time now is 15:00.