User Tools

Site Tools


notes:csharp:miscellaneous

Miscellaneous topics in .NET

CodeDOM

The Code Document Object Model (CodeDOM) is used to create an object graph that can be converted to a source file or to a binary assembly. A typical usage scenarios involve creating code for ASP.NET, Web Services, code wizards, designers, or any situations when we need to write repetitive code.

A single representation of a piece of code expressed in CodeDOM can be used to generate source code in multiple languages. The logical representation is basically a tree with containers: a topmost container (CodeCompileUnit) contains other elements such as namespaces, classes, methods, and individual statements.

Dynamic Source Code Generation and Compilation at MSDN

Example: Generate a simple C# source code file GeneratedCode.cs:

using Microsoft.CSharp; // CSharpCodeProvider
using System.CodeDom; // CodeDOM
using System.CodeDom.Compiler; // IndentedTextWriter
using System.IO; // StreamWriter
 
public class TestApp
{
    public static int Main(string[] args)
    {
        // The topmost container.
        CodeCompileUnit compileUnit = new CodeCompileUnit();
 
        // namespace Test
        CodeNamespace testNamespace = new CodeNamespace("Test");
 
        // using System;
        testNamespace.Imports.Add(new CodeNamespaceImport("System")); 
 
        // public class TestClass
        CodeTypeDeclaration testClass = new CodeTypeDeclaration("TestClass"); 
 
        // public static void Main()
        CodeEntryPointMethod main = new CodeEntryPointMethod(); 
 
        // Console.WriteLine("Hi!");
        CodeMethodInvokeExpression method1 = 
            new CodeMethodInvokeExpression(
                new CodeTypeReferenceExpression("Console"), 
                "WriteLine", 
                new CodePrimitiveExpression("Hi!"));
 
        // Add all pieces together.
        compileUnit.Namespaces.Add(testNamespace);
        testNamespace.Types.Add(testClass);
        testClass.Members.Add(main);
        main.Statements.Add(method1);
 
        // Set the bracing style for the generated source code.
        var opt = new CodeGeneratorOptions();
        opt.BracingStyle = "C";
 
        // Create a source file.
        CSharpCodeProvider provider = new CSharpCodeProvider(); 
        using (StreamWriter sw = new StreamWriter("GeneratedCode.cs", false))
        {
            using (IndentedTextWriter tw = new IndentedTextWriter(sw, "    "))
            {
                provider.GenerateCodeFromCompileUnit(compileUnit, tw, opt);
            }
        }
 
        return 0;
    }
}

GeneratedCode.cs:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.34003
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
 
namespace Test
{
    using System;
 
 
    public class TestClass
    {
 
        public static void Main()
        {
            Console.WriteLine("Hi!");
        }
    }
}

Command line arguments

Example: Enumerate command line arguments:

// the arguments are placed in a string[] array
foreach(string arg in Environment.GetCommandLineArgs())
{
    // ...
}

Conditional operators

The null conditional operator (C# 6) indicates that the code should not throw a NullReferenceException exception if the handler variable is null:

Delegate handler = null;
handler?.Invoke();
bool addressProvided = (order?.Customer?.Address != "");
 
// The above code is the equivalent of this code (although it may be not as safe as using the ? operator):
bool addressProvided = (order != null && order.Customer != null && order.Customer.Address != "");

The above is equivalent to the following code in previous versions of the C#:

Delegate handler = null;
if (handler != null)
{
    handler.Invoke();
}

Equivalent expressions with and without using a conditional operator:

int SomeMethod(int a, int b)
{
    return (a<b ? 1 : a>b ? 2 : 3);
}
int SomeMethod(int a, int b)
{
    if (a<b)
        return 1;
    else if (a>b)
      	return 2;
    else
      	return 3;
}

The conditional operator and nullable types:

int a = 0;
int b = 1;
 
int? x = (a == 0 ? new int?() : b); // x == null
 
// The following statement does not compile.
// Error: Type of conditional expression cannot be determined because there is no implicit 
// conversion between '<null>' and 'int'
int? x = (a == 0 ? null : b);

The null coalescing operator ?? returns the left operand if it's not null; otherwise, the right operand.

  • The first operand of the ?? operator must be a nullable type or reference type
  • The second operand of the ?? operator must be of the same type as the first or of a type that is implicitly convertible to the type of the first operant
  • If the first operant is not null, the expression has the value of the first operand
  • If the first oparand is null, the expression has the value of the second operand
  • The null coalescing operator is right associative
int? n = null;
 
// If n is null, assign -1 to i.
int i = n ?? -1;
 
// Null-coalescing operator is right associative. 
// It means that the first expression which is not null will be returned.
int? x = null;
int? z = null;
int y = x ?? z ?? -1; // chain null-coalescing operators
 
// If the type of the second operand is the underlying type of the first operand 
// then the result is that of the underlying type.
int? a = null;
int b = 5;
int c = a ?? b; // c == 5 of type int

Custom formatting

Example: Custom formatting using a ToString method that accepts a format string:

class Book
{
    public string ToString(string format)
    {
        // "G" represents a common format. It is the same as calling ToString().
        // A null value for the format string should also display the common format.
        if (String.IsNullOrWhiteSpace(format) || format == "G")
            format = "F";
 
        switch (format)
        {
            case "F": // full
                return Title + "; " + Authors + "; " + ISBN + "; " + String.Format("{0:C}", Price);
            case "T": // title only
                return Title;
            case "TA": // title and authors
                return Title + ", " + Authors;
            case "I": // isbn only
                return ISBN;
            default:
                throw new FormatException(String.Format("The '{0}' format is not supported.", format));
        }
    }
 
    public string Title { get; set; }
    public string Authors { get; set; }
    public decimal Price { get; set; }
    public float Rating { get; set; }
    public string ISBN { get; set; }
}
 
// Testing...
Book book = new Book();
book.Title = "Programming Windows";
book.Authors = "Charles Petzold";
book.Price = 44.30M;
book.Rating = 4.6f;
book.ISBN = "978-0735671768";
 
Console.WriteLine(book.ToString("F")); // Programming Windows; Charles Petzold; 978-0735671768; $44.30
Console.WriteLine(book.ToString("I")); // 978-0735671768
Console.WriteLine(book.ToString("T")); // Programming Windows
 
... but the overloaded ToString(string format, IFormatProvider provider) method does not work as expected:
Console.WriteLine("{0:F}", book); // returns the fully-qualified type name
Console.WriteLine("{0:I}", book); // returns the fully-qualified type name
Console.WriteLine("{0:T}", book); // returns the fully-qualified type name

Example: Custom formatting using the IFormattable.ToString method. This method accepts two parameters:

  • a format string
  • a format provider - a class implementing IFormatProvider
// [1] Derive your class from IFormattable.
class Book : IFormattable
{
    // ...
}
// [2] Implement the IFormattable interface by providing the ToString method with two parameters:
// The 'format' parameter is a string that specifies the requested format.
// The 'formatProvider' parameter is a reference to an object that implements IFormatProvider used
// to provide details such as culture-specific formatting. If this parameter is null, the culture 
// specified in the system settings is used.
public string ToString(string format, IFormatProvider provider)
{
   // ...
}

[3] Now you can call the overloaded ToString method. The output is the same as in the previous example.

Console.WriteLine("{0:F}", book);
Console.WriteLine("{0:I}", book);
Console.WriteLine("{0:T}", book);

Expression trees

Expression trees are representations of code in a tree-like data structure. They can be translated to something else (for example SQL) or they can be compiled and executed.

Expression trees can be used as a better performance alternative to reflection.

Expression Trees at MSDN

Example:

using System;
using System.Linq.Expressions;
 
public class TestApp
{
    public static int Main(string[] args)
    {
       // A call to Console.Write and Console.WriteLine.
       BlockExpression expr = Expression.Block(
           Expression.Call(
               null, 
               typeof(Console).GetMethod("Write", new Type[] { typeof(String) }),
               Expression.Constant("Hi ") ), 
                   Expression.Call(
                       null, 
                       typeof(Console).GetMethod("WriteLine", new Type[] { typeof(String) }), 
                       Expression.Constant("Burak!") ) ); 
 
        // Compile the expression to an Action and execute it.
        Expression.Lambda<Action>(expr).Compile()(); // displays "Hi Burak!"
 
        return 0;
    }
}

HttpClient

Since .NET 4.5 the HttpClient is a mechanism of choice for consuming HTTP web APIs and RESTful services. It is located in the System.Net.Http.dll assembly.

Example: Obtain HTML of a web page as a string:

using System.Net.Http;
...
using (HttpClient client = new HttpClient())
{
    string result = await client.GetStringAsync("http://www.wbswiki.com");
    Console.WriteLine(result);
}

Example: Execute multiple HTTP requests simultaneously:

using System.Net.Http;
...
HttpClient client = new HttpClient();
Task<string> task1 = client.GetStringAsync("http://www.wbswiki.com");
Task<string> task2 = client.GetStringAsync("http://www.wisenheimerbrainstorm.com/");
 
// Method #1
Console.WriteLine(await task1);
Console.WriteLine(await task2);
 
// Method #2
await Task.WhenAll(task1, task2);
Console.WriteLine(task1.Result);
Console.WriteLine(task2.Result);

Interlocked

The Interlocked class offers methods that change a variable value in a thread-safe manner:

  • Increment - increments a variable
  • Decrement - decrements a variable
  • Exchange - switches values
  • CompareExchange - checks whether the current value is correct and then changes it to the new value in one atomic operation; thanks to that, no other thread can change the value between comparing and exchanging it.

The Interlocked class guarantees that the increment and decrement operations are executed atomically.

using System;
using System.Threading;
 
namespace CSharpTest
{
    class Program
    {
        public static void Main()
        {
            Console.WriteLine("1. Create an instance of Counter.");
            Counter counter = new Counter();
            Console.WriteLine("");
 
            Console.WriteLine("2. Create four threads.");
            Thread thread1 = new Thread(new ThreadStart(counter.Increase));
            Thread thread2 = new Thread(new ThreadStart(counter.Increase));
            Thread thread3 = new Thread(new ThreadStart(counter.Decrease));
            Thread thread4 = new Thread(new ThreadStart(counter.Decrease));
            Console.WriteLine("");
 
            Console.WriteLine("3. Start the threads.");
            thread1.Start();
            thread2.Start();
            thread3.Start();
            thread4.Start();
 
            Console.ReadKey();
        }
    }
 
    // The 'count' parameter is sent by reference as it needs to be modified 
    // by Interlocked.Increment() and Interlocked.Decrement().
    internal class Counter
    {
        private long count = 0;
 
        public void Increase()
        {
            Interlocked.Increment(ref count);
            Console.WriteLine("   Counter = {0}", count);
        }
 
        public void Decrease()
        {
            Interlocked.Decrement(ref count);
            Console.WriteLine("   Counter = {0}", count);
        }
    }
}

Output:

1. Create an instance of Counter.
 
2. Create four threads.
 
3. Start the threads.
   Counter = 1
   Counter = 2
   Counter = 1
   Counter = 0

Lazy<T>

Example: Lazy initialization using Lazy<T>:

public class Order
{
    private Lazy<Customer> customer;
 
    public Order()
    {
        // Lazy<T> defaults to thread safe. If you don't need thread safety, 
        // set the isTreadSafe parameter to false.
        customer = new Lazy<Customer>(() => new Customer());
    }
 
    public Customer Customer
    {
        get
        {
            return customer.Value;
        }
    }
}
...
public class Customer
{
    // ...
}

Locking

When two threads simultaneously encounter a lock, one thread waits until the lock becomes available.

It is important to use the lock with a reference object that is private to the class, otherwise:

  • a value type would get boxed each time the lock is acquired
  • this variable might be used by other code to create a lock, causing a deadlock
  • a string variable might exists in several copies created by the compiler, so called string-interning (the compiler may create one object for several strings that have the same content)

The lock statement is a shortcut for calling the Enter and Exit methods of the System.Thread.Monitor class.

Example: Synchronize access to a block of code using the C# lock statement:

using System.Threading;
...
static void Main(string[] args)
{
    // Create an instance of WorkerClass.
    WorkerClass worker = new WorkerClass();
 
    // Create three secondary threads.
    Thread thread1 = new Thread(() => worker.Count());
    Thread thread2 = new Thread(() => worker.Count());
    Thread thread3 = new Thread(() => worker.Count());
 
    // Start all three threads.
    thread1.Start();
    thread2.Start();
    thread3.Start();
}
 
class WorkerClass
{
    // The object for the lock is private to the class.
    private object _locker = new object();
 
    public void Count()
    {
        // Only one thread at a time can execute this block of code.
        // In other words - the access to the block of code is synchronized.
        lock (_locker)
        {
            for (int i = 1; i < 10; i++)
            {
                Console.Write(i + " ");
                Thread.Sleep(500);
            }
            Console.WriteLine("");
        }
    }
}

Output:

1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9

Parallel class

The Parallel class offers methods that enable parallelization of work:

  • For
  • ForEach
  • Invoke

Example: Perform 10 parallel iterations:

using System.Threading;
using System.Threading.Tasks;
...
Parallel.For(0, 10, i =>
{
    Console.WriteLine(i); // 0 1 2 3 4 5 6 7 8 9
    Thread.Sleep(1000);
});

Example: Process a rectangular array of size W by H. The outer loop iteratates over columns (W 'width' is the number of columns) and the inner loop iterates over rows (H 'height' is the number of rows). It is opposite to what we would do if we processed the array serially.

Parallel.For(0, W, (int x, ParallelLoopState state) =>
{
    for (int y = 0; y < H; y++)
    {
        // Test is a method we call on each value of x and y.
        d[x, y] = Test(x, y);
    }
 
    // If you want to stop Parallel.For use state.Stop()
});

Example: Stop a parallel loop:

  • using the ParallelLoopState.Break method which ensures that all currently running iterations are finished
  • using the ParallelLoopState.Stop method which terminates currently running iterations
//
// Finish all currently running iterations.
//
ParallelLoopResult result1 =
    Parallel.For(0, 100, (int i, ParallelLoopState state) =>
    {
        if (i == 50)
        {
            Console.WriteLine("Break");
            state.Break();
        }
    });
 
Console.WriteLine("IsCompleted: " + result1.IsCompleted.ToString());
Console.WriteLine("LowestBreakIteration: " +
    (result1.LowestBreakIteration.HasValue ? result1.LowestBreakIteration.ToString() : "NULL"));
// Output:
// Break
// IsCompleted: False
// LowestBreakIteration: 50
 
//
// Terminate currently running iterations.
//
ParallelLoopResult result2 =
    Parallel.For(0, 100, (int i, ParallelLoopState state) =>
    {
        if (i == 50)
        {
            Console.WriteLine("Stop");
            state.Stop();
        }
    });
 
Console.WriteLine("IsCompleted: " + result2.IsCompleted.ToString());
Console.WriteLine("LowestBreakIteration: " +
    (result2.LowestBreakIteration.HasValue ? result2.LowestBreakIteration.ToString() : "NULL"));
// Output:
// Stop
// IsCompleted: False
// LowestBreakIteration: NULL    

Stopwatch

using System.Diagnostics;
...
Stopwatch timer = new Stopwatch();
 
timer.Reset();
timer.Start();
 
// do some work
System.Threading.Thread.Sleep(500);
 
timer.Stop();
Console.WriteLine(timer.ElapsedMilliseconds / 1000.0); // 0.5 sec
 
timer.Reset();
timer.Start();
 
// do some more work
System.Threading.Thread.Sleep(800);
 
timer.Stop();
Console.WriteLine(timer.ElapsedMilliseconds / 1000.0); // 0.8 sec

Frequently used properties of Stopwatch:

  • ElapsedMilliseconds
  • Elapsed.Seconds
  • Elapsed.TotalSeconds

Stream chaining

Example: A class UserScore illustrating stream chaining - serializing/encrypting and deserializing/decrypting:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
 
namespace Test
{
    [Serializable]
    class HighScoreEntry
    {
        public string Name { get; set; }
        public int Score { get; set; }
    }
 
    class UserScore
    {
        private List<HighScoreEntry> highScores = new List<HighScoreEntry>();
 
        // A key and a vector for encryption during serialization and deserialization.
        private byte[] cryptoKey = { 23, 45, 178, 45, 90, 34, 250, 11 };
        private byte[] cryptoVector = { 78, 73, 90, 2, 49, 204, 178, 108 };
 
        private void Serialize(string path)
        {
            DESCryptoServiceProvider des = new DESCryptoServiceProvider();
 
            using (FileStream fileStream = File.Open(path, FileMode.Create))
            {
                using (CryptoStream cryptoStream =
                    new CryptoStream(
                        fileStream,
                        des.CreateEncryptor(cryptoKey, cryptoVector),
                        CryptoStreamMode.Write))
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    formatter.Serialize(cryptoStream, highScores);
                }
            }
        }
 
        private void Deserialize(string path)
        {
            DESCryptoServiceProvider des = new DESCryptoServiceProvider();
 
            using (Stream fileStream = File.Open(path, FileMode.Open))
            {
                if (fileStream.Length > 0) // do not deserilize an empty file
                {
                    using (CryptoStream cryptoStream =
                        new CryptoStream(
                            fileStream,
                            des.CreateDecryptor(cryptoKey, cryptoVector),
                            CryptoStreamMode.Read))
                    {
                        BinaryFormatter formatter = new BinaryFormatter();
                        highScores = (List<HighScoreEntry>)formatter.Deserialize(cryptoStream);
                        // ...
                    }
                }
            }
        }
    }
}

Versioning

  • The assembly manifest stores its version number and the version numbers of all the assemblies that it references.
  • The format of the version number is MajorVersion.MinorVersion.BuildNumber.Revision

An assembly has two version numbers:

  • The .NET assembly version number represented by the AssemblyVersion attribute. This version number should be incremented manually. This is the version deployed to production.
  • The file version number represented by the AssemblyFileVersion attribute. This version number should be incremented with each build process on the build server.
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Visual Studio

A example of a post-build event command line:

xcopy "$(ProjectDir)..\MyProject\*.*" "$(TargetDir)" /Y

XML Documentation

Tags recognized by the C# compiler:

<c>          - inline code, e.g. <c>string str = "test";</c>
<code>       - code block
<example>    - code example
<exception>  - an exception class
<include>    - merges an external XML file that contains documentation
             - in the <include> element the path attribute denotes an XPath query to a specific element 
               in an external XML file that contains documentation
<list>       - a list
<para>       - a text paragraph
<param>      - a method parameter, e.g. <param name="title">description of title param</param>
<paramref>   - the name of the parameter to refer to, e.g. <paramref name="title"/>
<permission> - access to a member, <permission [cref="type"]>...</permission> 
             - indicates an IPermission type required by the type or member
<remarks>    - a remarks section
<returns>    - the return value of a method
<see>        - a cross-reference to another parameter, e.g. <see cref="member"/>...</see>
<seealso>    - a "see also" section, e.g. <seealso cref="member">...</seealso>
<summary>    - a summary section
<typeparam>, <typeparamref> - used with generics
<value>      - a description for the property 'value' parameter

You can define your own tags by simply using them.

When the C# compiler processes the XML comments, it generates <member> elements. Each member has a name with one of the following prefixes:

  • N: - namespace
  • T: - type (class, struct, enum, interface, delegate)
  • F: - field
  • M: - method
  • P: - property (including indexers)
  • E: - event
  • !: - error

The <list> element emits a bulleted, numbered, or table-style list:

<list type=[ bullet | number | table ]>
    <listheader>
        <term>...</term>
        <description>...</description>
    </listheader>
    <item>
        <term>...</term>
        <description>...</description>
    </item>
</list>
notes/csharp/miscellaneous.txt · Last modified: 2017/08/15 by leszek