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

Two examples of how to enumerate command line arguments:

static void Main(string[] args)
{
    Console.WriteLine($"There are {args.Length} arguments.");
 
    foreach (string arg in args)
    {
        Console.WriteLine(arg);
    }
}
// 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
...
private string msg;
public string Message
{
    get => msg ?? String.Empty;
    set => msg = value;
}
...

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);
}
using System.Net.Http;
...
using (var client = new HttpClient())
{
    var body = client.GetStringAsync("http://www.wbswiki.com");
    Console.WriteLine(body.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);

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
{
    // ...
}

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")]

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: 2020/10/14 by leszek