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!"); } } }
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()) { // ... }
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.
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; } ...
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:
// [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 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; } }
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);
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 { // ... }
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:
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); // ... } } } } } }
An assembly has two version numbers:
[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
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:
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>