CHAPTER 4 CLR AND BCL CHANGES 77 Native Code Enhancements I will not be covering changes to native code, so I have summarized some of the important changes here: • Support for real-time heap analysis. • New integrated dump analysis and debugging tools. • Tlbimp shared source is available from codeplex (http://clrinterop.codeplex.com/). • Support for 64-bit mode dump debugging has also been added. • Mixed mode 64-bit debugging is now supported, allowing you to transition from managed to native code. Exception Handling Exception handling has been improved in .NET4.0 with the introduction of the System.Runtime. ExceptionServices namespace, which contains classes for advanced exception handling. CorruptedStateExceptions Many developers (OK, I might have done this, too) have written code such as the following: try { // do something that may fail } catch(System.exception e) { } This is almost always a very naughty way to write code because all exceptions will be hidden. Hiding exceptions you don’t know about is rarely a good thing, and if you do know about them, you should inevitably be handling them in a better way. Additionally, there are some exceptions that should never be caught (even by lazy developers) such as lowdown beardy stuff such as access violations and calls to illegal instructions. These exceptions are potentially so dangerous that it’s best to just shut down the application as quick as possible before it can do any further damage. So in .NET 4.0, corrupted state exceptions will never be caught even if you specify a try a catch block. However, if you do want to enable catching of corrupted state exceptions application-wide (e.g., to route them to an error-logging class), you can add the following setting in your applications configuration file: LegacyCorruptedStateExceptionsPolicy=true This behavior can also be enabled on individual methods with the following attribute: [HandleProcessCorruptedStateExceptions] CHAPTER 4 CLR AND BCL CHANGES 78 New Types Now that the lowdown changes are out of the way, lets look at some of the new types in .NET4.0 and modifications to existing classes and methods. BigInteger Working with really big numbers in .NET can get a bit strange. For example, try the following example (without advanced options such as overflow checking) and you might be surprised at the result you get: int a = 2000000000; Console.WriteLine(a * 2); Console.ReadKey(); Surely the result is 4000000000? Running this code will give you the following answer: -294967296 NOTE VB.NET won't even let you compile the equivalent. This issue occurs due to how this type of integer is represented in binary and the overflow that occurs. After the multiplication, the number gets bigger than this type can handle, so it actually becomes negative. OK, so not many applications will need to hold values of this magnitude. But for those that do, .NET4.0 introduces the BigInteger class (in the System.Numerics namespace) that can hold really big numbers. BigInteger is an immutable type with a default value of 0 with no upper or lower bounds. This upper value is subject to available memory, of course, and if exceeded, an out-of-memory exception will be thrown. But seriously, what are you holding? Even the U.S. national debit isn’t that big. BigIntegers can be initialized in two main ways: BigInteger bigIntFromDouble = new BigInteger(4564564564542332); BigInteger assignedFromDouble = (BigInteger) 4564564564542332; BigInteger has a number of useful (and self-explanatory) methods not found in other numeric types: • IsEven() • IsOne() • IsPowerOfTwo() • IsZero() • IsSign() CHAPTER 4 CLR AND BCL CHANGES 79 Lazy<T> Lazy<T> allows you to easily add lazy initialization functionality to your variables. Lazy initialization saves allocating memory until the object is actually used. So if you never end up accessing your object, you have avoided using the resources to allocate it. Additionally, you have spread out resource allocation through your application’s life cycle, which is important for the responsiveness of UI-based applications. Lazy<T> couldn’t be easier to use: Lazy<BigExpensiveObject> instance; CAUTION Lazy has implications for multithreaded scenarios. Some of the constructors for the Lazy type have an isThreadSafe parameter (see MSDN for more details of this: http://msdn.microsoft.com/en-us/library/ dd997286%28VS.100%29.aspx ). Memory Mapping Files A memory mapped file maps the contents of a file into memory, allowing you to work with it in a very efficient manner. Memory mapped files can also be used for interprocess communication, allowing you to share information between two applications: Let’s see how to use memory mapped files inter process communication: 1. Create a new console application called Chapter4.MemoryMappedCreate. 2. Add the following using statements: using System.IO; using System.IO.MemoryMappedFiles; 3. Enter the following code in the Main() method: //Create a memory mapped file using (MemoryMappedFile MemoryMappedFile = MemoryMappedFile.CreateNew("test", 100)) { MemoryMappedViewStream stream = MemoryMappedFile.CreateViewStream(); using (BinaryWriter writer = new BinaryWriter(stream)) { writer.Write("hello memory mapped file!"); } Console.WriteLine("Press any key to close mapped file"); Console.ReadKey(); } 4. Add another Console application called Chapter4.MemoryMappedRead to the solution. 5. Add the following using statements: using System.IO; using System.IO.MemoryMappedFiles; CHAPTER 4 CLR AND BCL CHANGES 80 6. Enter the following code in the Main() method: //Read a memory mapped file using (MemoryMappedFile MemoryMappedFile = MemoryMappedFile.OpenExisting("test")) { using (MemoryMappedViewStream Stream = MemoryMappedFile.CreateViewStream()) { BinaryReader reader = new BinaryReader(Stream); Console.WriteLine(reader.ReadString()); } Console.ReadKey(); } 7. You have to run both projects to demonstrate memory mapped files. First, right-click the project Chapter4.MemoryMappedCreate and select Debug Start new instance. A new memory mapped file will be created and a string written to it. 8. Right-click the project Chapter4.MemoryMappedRead and select Debug Start new instance. You should see the string hello memory mapped file! read and printed from the other project. The other main use of memory mapped files is for working with very large files. For an example, please refer to this MSDN article: http://msdn.microsoft.com/en- us/library/system.io.memorymappedfiles.memorymappedfile(VS.100).aspx. SortedSet<T> Sorted set is a new type of collection in the System.Collections.Generic namespace that maintains the order of items as they are added. If a duplicate item is added to a sorted set, it will be ignored, and a value of false is returned from the SortedSet’s Add() method. The following example demonstrates creating a sorted list of integers with a couple of duplicates in: SortedSet<int> MySortedSet = new SortedSet<int> { 8, 2, 1, 5, 10, 5, 10, 8 }; ISet<T> Interface .NET4.0 introduces ISet<T>, a new interface utilized by SortedSet and HashSet and surprisingly enough for implementing set classes. Tuple A tuple is a typed collection of fixed size. Tuples were introduced for interoperability with F# and IronPython, but can also make your code more concise. Tuples are very easy to create: Tuple<int, int, int, int, int> MultiplesOfTwo = Tuple.Create(2, 4, 6, 8, 10); Individual items in the tuple can then be queried with the Item property: Console.WriteLine(MultiplesOfTwo.Item2); CHAPTER 4 CLR AND BCL CHANGES 81 Tuples might contain up to seven elements; if you want to add more items, you have to pass in another tuple to the Rest parameter: var multiples = new Tuple<int, int, int, int, int, int, int,Tuple<int,int,int>>(2, 4, 6, 8, 10, 12, 14, new Tuple<int,int,int>(3,6,9)); Items in the second tuple can be accessed by querying the Rest property: Console.WriteLine(multiples.Rest.Item1); System.Numerics.Complex Mathematicians will be glad of the addition of the new Complex type: a structure for representing and manipulating complex numbers, meaning that they will no longer have to utilize open source libraries or projects. Complex represents both a real and imaginary number, and contains support for both rectangular and polar coordinates: Complex c1 = new Complex(8, 2); Complex c2 = new Complex(8, 2); Complex c3 = c1 + c2; And I am afraid my math skills aren’t up to saying much more about this type, so let’s move on. System.IntPtr and System.UIntPtr Addition and subtraction operators are now supported for System.IntPtr and System.UIntPtr. Add()() and Subtract() methods have also been added to these types. Tail Recursion The CLR contains support for tail recursion, although this is only currently accessible through F#. Changes to Existing Functionality .NET4.0 enhances a number of existing commonly used methods and classes. Action and Func Delegates Action and Func delegates now can accept up to 16 generic parameters, which might result in unreadable code. This reminds me of an API that a health care provider (who shall remain nameless) gave me that had a method with more than 70 (?!) parameters. Compression Improvements The 4 GB size limit has been removed from System.IO.Compression methods. The compression methods in DeflateStream and GZipStream do not try to compress already compressed data, resulting in better performance and compression ratios. CHAPTER 4 CLR AND BCL CHANGES 82 File IO .NET4.0 introduces a new method to the File class called File.ReadLines()(), which returns IEnumerable<string> rather than a string array. Reading a file one line at a time is much more efficient than loading the entire contents into memory (File.ReadAllLines()). This technique also has the advantage that you can start working with a file immediately and bail out of the loop when you find what you are looking for without having to read the entire file. ReadLines()() is now the preferred method for working with very large files. IEnumerable<string> FileContent=File.ReadLines("MyFile.txt"); foreach(string Line in FileContent) { Console.Write(Line); } File.WriteAllLines()() and File.AppendAllLines()() now take an IEnumerable<string> as an input parameter. System.IO.Directory has the following new static methods that return IEnumerable<string>: • EnumerateDirectories(path) • EnumerateFiles(path) • EnumerateFileSystemEntries(path) System.IO.DirectoryInfo has the following new static methods: • EnumerateDirectories(path) returns IEnumerable<DirectoryInfo> • EnumerateFiles(path) returns IEnumerable<FileInfo> • EnumerateFileSystemInfos(path) returns IEnumerable<FileSystemInfo> Enumerating files within a directory is also now more efficient than previously because file metadata such as a creation date is queried during enumeration. This means that Windows API calls should be unnecessary because the information is already retrieved. Path.Combine() The Path.Combine() method has new overloads that accept three and four parameters and an array of strings. Isolated Storage The default space allocation for isolated storage has now been doubled. Registry Access Changes The RegistryKey.CreateSubKey() method has a new option that allows the passing in of a RegistryOptions enumeration, allowing you to create a volatile registry key. Volatile registry keys are not persisted during reboots, so they are great for storing temporary data in. The following code shows how to create a volatile registry key: CHAPTER 4 CLR AND BCL CHANGES 83 var key = instance.CreateSubKey(subkey, RegistryKeyPermissionCheck.Default, RegistryOptions.Volatile); In 64-bit versions of Windows, data is stored separately in the registry for 32- and 64-bit applications. OpenBaseKey()() and OpenRemoteBaseKey() methods now allow you to specify a new enum called RegistryView for specifying the mode that should be used when reading. Note that if you mistakenly use the 64-bit option on a 32-bit version, you will get the 32-bit view. Stream.CopyTo() Stream.CopyTo() allows you to copy the contents of one stream to another, avoiding some tedious coding: MemoryStream destinationStream = new MemoryStream(); using (FileStream sourceStream = File.Open(@"c:\temp.txt", FileMode.Open)) { sourceStream.CopyTo(destinationStream); } Guid.TryParse(), Version.TryParse(), and Enum.TryParse<T>() New TryParse() methods have been added to Guid, Version and Enum types: Guid myGuid; Guid.TryParse("not a guid", out myGuid); Enum.HasFlag() Returns a Boolean value indicating whether one or more flags are set on an enum. For example, you could use the HasFlag() method to test whether a car has particular options set: [Flags] public enum CarOptions { AirCon = 1, Turbo = 2, MP3Player = 4 } static void Main(string[] args) { CarOptions myCar = CarOptions.MP3Player | CarOptions.AirCon | CarOptions.Turbo; Console.WriteLine("Does car have MP3? {0}", myCar.HasFlag(CarOptions.MP3Player)); Console.ReadKey(); } . * 2); Console.ReadKey(); Surely the result is 40 000 000 00? Running this code will give you the following answer: -2 949 67296 NOTE VB .NET won't even let you compile the equivalent .NET can get a bit strange. For example, try the following example (without advanced options such as overflow checking) and you might be surprised at the result you get: int a = 200 000 000 0;. main ways: BigInteger bigIntFromDouble = new BigInteger (45 645 645 645 42332); BigInteger assignedFromDouble = (BigInteger) 45 645 645 645 42332; BigInteger has a number of useful (and self-explanatory)