Powerworkshop C# 4 at BASTA Spring 2011

20 February 2011 - .NET

Tomorrow I will do a power workshop about C# 4 at BASTA Spring 2011 in Darmstadt. Here is the abstract of the workshop as shown in the conference guide (German):

C# 4 steckt für viele Entwickler immer noch voller Geheimnisse und Überraschungen. Wussten Sie, dass sich fast alle foreach-Schleifen durch LINQ ersetzen lassen? Dass der Zugriff auf Office und generell COM-Bibliotheken mit C# 4 zum Kinderspiel wurde? Dass C# 4 voller Möglichkeiten steckt, Ihre Programme zu parallelisieren?

Wenn in Ihrer täglichen Arbeit die Vorteile der aktuellen C#-Version noch nicht in Fleisch und Blut übergegangen sind, sind Sie in diesem Workshop richtig. Ihr Trainer, Rainer Stropek, konzentriert sich auf praktische Beispiele, Tipps und Tricks, die Ihnen während des Workshops auch zum Mitmachen zur Verfügung stehen.

 In the workshop I will cover the following topics:

  1. What's new in the Microsoft Visual Studio 2010 C# IDE for developers?
  2. What's new for COM and Office Interop in C# 4?
  3. Parallel computing with TPL and PLINQ
  4. The dynamic keyword and the Dynamic Language Runtime
  5. Managed Extensibility Framework (MEF)

You can download the slide deck of the workshop as a PDF file.

What's new in the Microsoft Visual Studio 2010 C# IDE for developers?

A few weeks ago I did a half-day workshop for C# developers about the Visual Studio 2010 IDE in Munich. This workshop has been especially for people who are new with development in Visual Studio. As the power workshop at BASTA will be for experienced developers who want to get up to date with C# 4 I will just do parts of the workshop from Munich (only the new things that came with VS2010). During the workshop I will demo the following new features of VS2010:

  1. Some small nice details like the new project dialog, async. reference dialog, etc.
  2. Editor news like ad hoc code blocks, zooming, etc.
  3. Navigate To
  4. Call Hierarchy
  5. Code Definition Window
  6. New IntelliSense suggestion mode
  7. Generate from usage
  8. New windowing functions
  9. Data tips
  10. DLR support in the debugger
  11. IntelliTrace

For the demos I will use sample code from the Visual Studio 2010 and .NET Framework 4 Training Kit.

What's new for COM and Office Interop in C# 4?

The second part of the workshop will be dedicated to features regarding to COM interop. They are especially useful if you want to write interop code for Microsoft Office. I will speak about and demo the following functions of the C# 4 compiler:

  1. Embedded interop types
  2. Named and optional parameters

During the workshop I will use the following example project: OfficeInterop

Parallel computing with TPL and PLINQ

One of the big new things in C# 4 is the enhanced support for parallel computing. In the workshop I will cover the following aspects of parallel programming in C# 4:

  1. Task Parallel Library (TPL)
    1. Task and Parallel class
    2. Collections for parallel programming
  2. Parallel LINQ (PLINQ)

Because of the importance of the topic I will not only speak about TPL and PLINQ in theory. I will show quite a few small live-coded samples. Additionally we will take a look at the new possibilities in a large practical hands-on sample. If you want to participate you will need the following things:

  1. Download and extract geo data about Berlin from the Open Street Map project (http://download.geofabrik.de/osm/europe/germany/).
  2. Get a server with >=4 cores (if you don't have a physical one use e.g. a trial account for Windows Azure to get your hands on one; I will do that during the workshop in Darmstadt).
  3. Create a SQL Server database with the following script (if you don't have a powerful SQL Server on your laptop you could e.g. use also Windows Azure):

    CREATE TABLE [dbo].[Highway](
     [HighwayID] [int] NOT NULL,
     [HighwayGeo] [geography] NULL,
     [HighwayType] [nvarchar](100) NULL,
     [StartingNodeID] [int] NULL,
     [EndNodeID] [int] NULL,
     PRIMARY KEY CLUSTERED ( [HighwayID] ASC )
    )
  4. Get my sample solution AzureDynamicLoaderCommon.
  5. Follow the steps that I show during the workshop.

During the workshop I might use some of my simple parallel programming demos that you can find in the sample solution ParallelProgramming.Samples.

Note: It would not be possible to deploy many different versions of the sample (e.g. with TPL, with LINQ, with PLINQ) multiple times. In Azure this would take me 15 minutes for each deployment. Therefore I have written a generic Azure worker role that is able to load an assembly from blob store and execute it. With this I just have to deploy a single app; the different algorithms are started by uploading the corresponding assembly into blob store and starting it using a message in an Azure queue (e.g. with Azure Storage Explorer or Cerebrata Cloud Storage Studio). Just in case you are interested - here is the code of the two main classes that perform this "deployment magic":

using System;
using System.Net;
using System.Threading;
using AzureDynamicLoader.Common;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace ADL.Wrk
{
    public class WorkerRole : RoleEntryPoint
    {
        public override void Run()
        {
            // Local helper variables
            var exeAssembly = typeof(Bootstrapper).Assembly.FullName;
            AppDomain subDomain = null;

            #region Setup storage
            var account = CloudStorageHelper.GetCloudStorageAccount();
            
            var tableClient = CloudStorageHelper.GetCloudTableClient(account);
            tableClient.CreateTableIfNotExist(ConfigurationCache.Current.LogTableName);
            var logContext = tableClient.GetDataServiceContext();

            var queueClient = CloudStorageHelper.GetCloudQueueClient(account);
            var enablerQueue = queueClient.GetQueueReference(ConfigurationCache.Current.EnablerQueueName);
            enablerQueue.CreateIfNotExist();
            var disablerQueue = queueClient.GetQueueReference(ConfigurationCache.Current.DisablerQueueName);
            disablerQueue.CreateIfNotExist();
            #endregion

            while (true)
            {
                try
                {
                    if (subDomain == null)
                    {
                        #region Wait for enabler message
                        var enableMsg = enablerQueue.GetMessage();
                        if (enableMsg != null)
                        {
                            var assemblyName = enableMsg.AsString;
                            enablerQueue.DeleteMessage(enableMsg);

                            subDomain = AppDomain.CreateDomain(
                                "Subdomain",
                                null,
                                new AppDomainSetup() { ApplicationBase = System.Environment.CurrentDirectory });
                            var bootstrapper = (Bootstrapper)subDomain.CreateInstanceAndUnwrap(
                                exeAssembly,
                                typeof(Bootstrapper).FullName);
                            if (!bootstrapper.TryStartup(assemblyName))
                            {
                                AppDomain.Unload(subDomain);
                                subDomain = null;
                            }
                        }
                        #endregion
                    }
                    else
                    {
                        #region Wait for disabler message
                        var disableMsg = disablerQueue.GetMessage();
                        if (disableMsg != null)
                        {
                            disablerQueue.DeleteMessage(disableMsg);
                            try
                            {
                                AppDomain.Unload(subDomain);
                            }
                            finally
                            {
                                subDomain = null;
                            }
                        }
                        #endregion
                    }

                    Thread.Sleep(10000);
                }
                catch (Exception ex)
                {
                    CloudStorageHelper.WriteLog(logContext, ex);
                }
            }
        }

        public override bool OnStart()
        {
            // Set the maximum number of concurrent connections 
            ServicePointManager.DefaultConnectionLimit = 12;

            // For information on handling configuration changes
            // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.

            return base.OnStart();
        }
    }
}
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using AzureDynamicLoader.Common;

namespace ADL.Wrk
{
    internal class Bootstrapper : MarshalByRefObject
    {
        public bool TryStartup(string assemblyName)
        {
            var account = CloudStorageHelper.GetCloudStorageAccount();
            var logContext = CloudStorageHelper.GetCloudTableClient(account).GetDataServiceContext();

            try
            {
                // Check if assembly name also contains a type name
                var typeName = string.Empty;
                if (assemblyName.Contains(';'))
                {
                    var nameParts = assemblyName.Split(';');
                    assemblyName = nameParts[0];
                    typeName = nameParts[1];
                }

                // Get assembly from blob store
                CloudStorageHelper.WriteLog(logContext, string.Format("Trying to load {0} from blob store", assemblyName));
                var blobClient = CloudStorageHelper.GetCloudBlobClient(account);
                var assemblyBlob = blobClient
                    .GetContainerReference(ConfigurationCache.Current.ContainerName)
                    .GetBlobReference(assemblyName);
                var binaryAssembly = assemblyBlob.DownloadByteArray();

                // Load assembly needed for geo operations (SQL Server Feature Pack)
                Assembly.LoadFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Microsoft.SqlServer.Types.dll"));
                Assembly.Load(typeof(IObserver<,>).Assembly.FullName);
                Assembly.Load(typeof(ObservableExtensions).Assembly.FullName);
                Assembly.Load(typeof(EnumerableEx).Assembly.FullName);
                var assembly = Assembly.Load(binaryAssembly);

                // Look for startup type (has to implement IStartup)
                CloudStorageHelper.WriteLog(logContext, "Looking for type in dynamically loaded assembly");
                var startupType = assembly.GetTypes().Where(t => (typeName.Length == 0 || t.Name == typeName) && typeof(IStartup).IsAssignableFrom(t)).FirstOrDefault();
                if (startupType != null)
                {
                    CloudStorageHelper.WriteLog(logContext, string.Format("Found type {0}", startupType.FullName));

                    // Setup logging queue
                    var logQueue = new BlockingCollection<string>(5000);
                    Task.Factory.StartNew(() =>
                        {
                            foreach (var logItem in logQueue.GetConsumingEnumerable())
                            {
                                CloudStorageHelper.WriteLog(logContext, logItem);
                            }
                        });

                    // Create instance of startup type and start Run method in a separate thread
                    var startupObject = Activator.CreateInstance(startupType) as IStartup;
                    CloudStorageHelper.WriteLog(logContext, "Starting dynamically loaded component async.");
                    Task.Factory.StartNew(() =>
                        {
                            try
                            {
                                startupObject.Run(
                                    s => logQueue.Add(s),
                                    ex => logQueue.Add(CloudStorageHelper.GetExceptionText(ex)));
                            }
                            catch (Exception ex)
                            {
                                CloudStorageHelper.WriteLog(CloudStorageHelper.GetCloudTableClient().GetDataServiceContext(), ex);
                            }
                        });
                    CloudStorageHelper.WriteLog(logContext, "Launched dynamically loaded component async.");

                    // Return true to indicate that worker thread has been started successfully
                    return true;
                }
                else
                {
                    CloudStorageHelper.WriteLog(logContext, "Did not find a type that implements IStartup");
                }
            }
            catch (Exception ex)
            {
                CloudStorageHelper.WriteLog(logContext, ex);
            }

            // Return false to indicate that there has been an error
            return false;
        }
    }
}

The dynamic keyword and the Dynamic Language Runtime

The support of the DLR in general and the dynamic keyword in particular will be the next part of the workshop. dynamic is especially useful when working with dynamic type systems (e.g. COM, HTML DOM, Python, etc.). In the workshop I will firstly describe the basics of the DLR (e.g. expression trees, call sites, etc.). Step by step we will implement our own tiny little version of ExpandoObject. Last but not least I will show practical applications of the DLR together with IronPython in a WPF application.

Here is the source code of our own demo implementation of IDynamicMetaObjectProvider:

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq.Expressions;

namespace DynamicPropertyBag
{
    public class PropertyBag : IDynamicMetaObjectProvider
    {
        private Dictionary<string, dynamic> values = new Dictionary<string, dynamic>();

        public PropertyBag()
        {
        }

        protected PropertyBag(IEnumerable<KeyValuePair<object, object>> properties)
            : this()
        {
            foreach (var prop in properties)
            {
                this.SetMember(prop.Key.ToString(), prop.Value);
            }
        }

        public dynamic GetMember(string name)
        {
            return this.values[name];
        }

        public void SetMember(string name, dynamic value)
        {
            this.values[name] = value;
        }

        public DynamicMetaObject GetMetaObject(Expression parameter)
        {
            return new DynamicGetSetMetaObject<PropertyBag>(this, parameter);
        }

        private class DynamicGetSetMetaObject<T> : System.Dynamic.DynamicMetaObject
        {
            public DynamicGetSetMetaObject(T v, Expression e)
                : base(e, BindingRestrictions.Empty, v)
            {
            }

            public override DynamicMetaObject BindGetMember(GetMemberBinder info)
            {
                var x = this.Expression;
                var test = Expression.TypeIs(x, typeof(T));
                var target = Expression.Call(
                                  Expression.Convert(x, typeof(T)),
                                  typeof(T).GetMethod("GetMember"),
                                  Expression.Constant(info.Name));
                return new System.Dynamic.DynamicMetaObject(target, BindingRestrictions.GetExpressionRestriction(test));
            }

            public override DynamicMetaObject BindSetMember(SetMemberBinder info, DynamicMetaObject value)
            {
                var expression = Expression.TypeIs(this.Expression, typeof(T));
                var target = Expression.Block(
                    Expression.Call(
                        Expression.Convert(this.Expression, typeof(T)),
                        typeof(T).GetMethod("SetMember"),
                        Expression.Constant(info.Name),
                        Expression.Convert(value.Expression, typeof(object))),
                    Expression.Convert(value.Expression, typeof(object)));
                return new System.Dynamic.DynamicMetaObject(target, BindingRestrictions.GetExpressionRestriction(expression));
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            dynamic myObj = new PropertyBag();
            myObj.FirstName = "Rainer";
            myObj.LastName = "Stropek";

            Console.WriteLine(myObj.LastName);
        }
    }
}

The DLR + IronPython sample that brings everything together demonstrates the following features:

  1. Execute Python scripts from a C# application
  2. Dynamic UI using WPF and Python
  3. Adding custom business logic to an existing app using Python and data binding
  4. Python-based WPF converter
  5. Adding Excel export functions to a WPF application using Python

Interested in the source code. Everything is in the slides (see top of this article). You can also download the Visual Studio solution and try it.

Managed Extensibility Framework (MEF)

Depending on the time and the interest of the participants I will close the workshop with a (more or less deep) introduction of MEF. This part of the C# 4 workshop will be similar to what I showed in Munich. You can find the sample code and hands on labs in the corresponding blog article.