Chapter 12

Dynamic Language Extensions

WHAT’S IN THIS CHAPTER?

  • Understanding the Dynamic Language Runtime
  • The dynamic type
  • The DLR ScriptRuntime
  • Creating dynamic objects with DynamicObject and ExpandoObject

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

The wrox.com code downloads for this chapter are found at http://www.wrox.com/remtitle.cgi?isbn=1118314425 on the Download Code tab. The code for this chapter is divided into the following major examples:

  • DLRHost
  • Dynamic
  • DynamicFileReader
  • ErrorExample

The growth of languages such as Ruby and Python, and the increased use of JavaScript, have intensified interest in dynamic programming. In previous versions of the .NET Framework, the var keyword and anonymous methods started C# down the “dynamic” road. In version 4, the dynamic type was added. Although C# is still a statically typed language, these additions give it the dynamic capabilities that some developers are looking for.

In this chapter, you’ll look at the dynamic type and the rules for using it. You’ll also see what an implementation of DynamicObject looks like and how it can be used. ExpandoObject, which is the frameworks implementation of DynamicObject, will also be covered.

DYNAMIC LANGUAGE RUNTIME

The dynamic capabilities of C# 4 are part of the dynamic language runtime (DLR). The DLR is a set of services that is added to the common language runtime (CLR) to enable the addition of dynamic languages such as Ruby and Python. It also enables C# to take on some of the same dynamic capabilities that these dynamic languages have.

There is a version of the DLR that is open source and resides on the CodePlex website. This same version is included with the .NET 4.5 Framework, with some additional support for language implementers.

In the .NET Framework, the DLR is found in the System.Dynamic namespace as well as a few additional classes in the System.Runtime.CompilerServices namespace.

IronRuby and IronPython, which are open-source versions of the Ruby and Python languages, use the DLR. Silverlight also uses the DLR. It’s possible to add scripting capabilities to your applications by hosting the DLR. The scripting runtime enables you to pass variables to and from the script.

THE DYNAMIC TYPE

The dynamic type enables you to write code that bypasses compile-time type checking. The compiler will assume that whatever operation is defined for an object of type dynamic is valid. If that operation isn’t valid, the error won’t be detected until runtime. This is shown in the following example:

  class Program
{
    static void Main(string[] args)
    {
        var staticPerson = new Person();
        dynamic dynamicPerson = new Person();
        staticPerson.GetFullName("John", "Smith");
        dynamicPerson.GetFullName("John", "Smith");
    }
}
   
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string GetFullName()
    {
        return string.Concat(FirstName, " ", LastName);
    }
}

This example will not compile because of the call to staticPerson.GetFullName. There isn’t a method on the Person object that takes two parameters, so the compiler raises the error. If that line of code were to be commented out, the example would compile. If executed, a runtime error would occur. The exception that is raised is RuntimeBinderException. The RuntimeBinder is the object in the runtime that evaluates the call to determine whether Person really does support the method that was called. Binding is discussed later in the chapter.

Unlike the var keyword, an object that is defined as dynamic can change type during runtime. Remember that when the var keyword is used, the determination of the object’s type is delayed. Once the type is defined, it can’t be changed. Not only can you change the type of a dynamic object, you can change it many times. This differs from casting an object from one type to another. When you cast an object, you are creating a new object with a different but compatible type. For example, you cannot cast an int to a Person object. In the following example, you can see that if the object is a dynamic object, you can change it from int to Person:

dynamic dyn;
          
dyn = 100;
Console.WriteLine(dyn.GetType());
Console.WriteLine(dyn);
   
dyn = "This is a string";
Console.WriteLine(dyn.GetType());
Console.WriteLine(dyn);
            
dyn = new Person() { FirstName = "Bugs", LastName = "Bunny" };
Console.WriteLine(dyn.GetType());
Console.WriteLine("{0} {1}", dyn.FirstName, dyn.LastName);

Executing this code would show that the dyn object actually changes type from System.Int32 to System.String to Person. If dyn had been declared as an int or string, the code would not have compiled.

Note a couple of limitations to the dynamic type. A dynamic object does not support extension methods. Nor can anonymous functions (lambda expressions) be used as parameters to a dynamic method call, so LINQ does not work well with dynamic objects. Most LINQ calls are extension methods, and lambda expressions are used as arguments to those extension methods.

Dynamic Behind the Scenes

So what’s going on behind the scenes to make this happen? C# is still a statically typed language. That hasn’t changed. Take a look at the IL (Intermediate Language) that’s generated when the dynamic type is used.

First, this is the example C# code that you’re looking at:

using System;
   
namespace DeCompile
{
    class Program
    {
        static void Main(string[] args)
        {
            StaticClass staticObject = new StaticClass();
            DynamicClass dynamicObject = new DynamicClass();
            Console.WriteLine(staticObject.IntValue);
            Console.WriteLine(dynamicObject.DynValue);
            Console.ReadLine();
        }
    }
   
    class StaticClass
    {
        public int IntValue = 100;
    }
   
    class DynamicClass
    {
        public dynamic DynValue = 100;
    }
}

You have two classes, StaticClass and DynamicClass. StaticClass has a single field that returns an int. DynamicClass has a single field that returns a dynamic object. The Main method just creates these objects and prints out the value that the methods return. Simple enough.

Now comment out the references to the DynamicClass in Main like this:

static void Main(string[] args)
{
    StaticClass staticObject = new StaticClass();
    //DynamicClass dynamicObject = new DynamicClass();
    Console.WriteLine(staticObject.IntValue);
    //Console.WriteLine(dynamicObject.DynValue);
    Console.ReadLine();
}

Using the ildasm tool (discussed in Chapter 19, “Assemblies”), you can look at the IL that is generated for the Main method:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       26 (0x1a)
  .maxstack  1
  .locals init ([0] class DeCompile.StaticClass staticObject)
  IL_0000:  nop
  IL_0001:  newobj     instance void DeCompile.StaticClass::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldfld      int32 DeCompile.StaticClass::IntValue
  IL_000d:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0012:  nop
  IL_0013:  call       string [mscorlib]System.Console::ReadLine()
  IL_0018:  pop
  IL_0019:  ret
} // end of method Program::Main

Without going into the details of IL but just looking at this section of code, you can still pretty much tell what’s going on. Line 0001, the StaticClass constructor, is called. Line 0008 calls the IntValue field of StaticClass. The next line writes out the value.

Now comment out the StaticClass references and uncomment the DynamicClass references:

static void Main(string[] args)
{
    //StaticClass staticObject = new StaticClass();
    DynamicClass dynamicObject = new DynamicClass();
    Console.WriteLine(staticObject.IntValue);
    //Console.WriteLine(dynamicObject.DynValue);
    Console.ReadLine();
}

Compile the application again and this is what is generated:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       121 (0x79)
  .maxstack  9
  .locals init ([0] class DeCompile.DynamicClass dynamicObject,
           [1] class 
[Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[]
                  CS$0$0000)
  IL_0000:  nop
  IL_0001:  newobj     instance void DeCompile.DynamicClass::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite'1
                       <class [mscorlib]
System.Action'3<class 
[System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]
System.Type,object>> DeCompile.Program/'<Main>o__SiteContainer0'::'<>p__Site1'
  IL_000c:  brtrue.s   IL_004d
  IL_000e:  ldc.i4.0
  IL_000f:  ldstr "WriteLine"
  IL_0014:  ldtoken    DeCompile.Program
  IL_0019:  call       class [mscorlib]System.Type 
[mscorlib]System.Type::GetTypeFromHandle
(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_001e:  ldnull
  IL_001f:  ldc.i4.2
  IL_0020:  newarr     
[Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
  IL_0025:  stloc.1
  IL_0026:  ldloc.1
  IL_0027:  ldc.i4.0
  IL_0028:  ldc.i4.s   33
  IL_002a:  ldnull
  IL_002b:  newobj     instance void [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder
.CSharpArgumentInfo::.ctor(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder
.CSharpArgumentInfoFlags, string)
  IL_0030:  stelem.ref
  IL_0031:  ldloc.1
  IL_0032:  ldc.i4.1
  IL_0033:  ldc.i4.0
  IL_0034:  ldnull
  IL_0035:  newobj     instance void [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder
.CSharpArgumentInfo::.ctor(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder
.CSharpArgumentInfoFlags, string)
  IL_003a:  stelem.ref
  IL_003b:  ldloc.1
  IL_003c:  newobj     instance void [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder
.CSharpInvokeMemberBinder::.ctor(valuetype Microsoft.CSharp]Microsoft.CSharp
.RuntimeBinder.CSharpCallFlags, string)
class [mscorlib]System.Type,
class [mscorlib]System.Collections.Generic.IEnumerable'1
<class [mscorlib]System.Type>,
class [mscorlib]System.Collections.Generic.IEnumerable'1
<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
  IL_0041:  call       class [System.Core]System.Runtime.CompilerServices.CallSite'1
<!0> class [System.Core]System.Runtime.CompilerServices.CallSite'1
<class [mscorlib]System.Action'3
<class [System.Core]System.Runtime.CompilerServices.CallSite,
class [mscorlib]System.Type,object>>::Create(class 
[System.Core]System.Runtime.CompilerServices.CallSiteBinder)
  IL_0046:  stsfld     class [System.Core]System.Runtime.CompilerServices.CallSite'1
<class [mscorlib]System.Action'3
<class [System.Core]System.Runtime.CompilerServices.CallSite,
class [mscorlib]System.Type,object>> 
DeCompile.Program/'<Main>o__SiteContainer0'::'<>p__Site1'
  IL_004b:  br.s       IL_004d
  IL_004d:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite'1
<class [mscorlib]System.Action'3
<class [System.Core]System.Runtime.CompilerServices.CallSite,
class [mscorlib]System.Type,object>> 
DeCompile.Program/'<Main>o__SiteContainer0'::'<>p__Site1'
  IL_0052:  ldfld      !0 class [System.Core]System.Runtime.CompilerServices.CallSite'1
<class [mscorlib]System.Action'3
<class [System.Core]System.Runtime.CompilerServices.CallSite,
class [mscorlib]System.Type,object>>::Target
  IL_0057:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite'1
<class [mscorlib]System.Action'3
<class [System.Core]System.Runtime.CompilerServices.CallSite,
class [mscorlib]System.Type,object>> 
DeCompile.Program/'<Main>o__SiteContainer0'::'<>p__Site1'
  IL_005c:  ldtoken    [mscorlib]System.Console
  IL_0061:  call       class [mscorlib]System.Type 
[mscorlib]System.Type::GetTypeFromHandle
(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_0066:  ldloc.0
  IL_0067:  ldfld      object DeCompile.DynamicClass::DynValue
  IL_006c:  callvirt   instance void class [mscorlib]System.Action'3
             <class [System.Core]System.Runtime.CompilerServices.CallSite, class 
             [mscorlib]System.Type,object>::Invoke(!0,!1,!2)
  IL_0071:  nop
  IL_0072:  call       string [mscorlib]System.Console::ReadLine()
  IL_0077:  pop
  IL_0078:  ret
} // end of method Program::Main

It’s safe to say that the C# compiler is doing a little extra work to support the dynamic type. Looking at the generated code, you can see references to System.Runtime.CompilerServices.CallSite and System.Runtime.CompilerServices.CallSiteBinder.

The CallSite is a type that handles the lookup at runtime. When a call is made on a dynamic object at runtime, something has to check that object to determine whether the member really exists. The call site caches this information so the lookup doesn’t have to be performed repeatedly. Without this process, performance in looping structures would be questionable.

After the CallSite does the member lookup, the CallSiteBinder is invoked. It takes the information from the call site and generates an expression tree representing the operation to which the binder is bound.

There is obviously a lot going on here. Great care has been taken to optimize what would appear to be a very complex operation. Clearly, although using the dynamic type can be useful, it does come with a price.

HOSTING THE DLR SCRIPTRUNTIME

Imagine being able to add scripting capabilities to an application, or passing values in and out of the script so the application can take advantage of the work that the script does. These are the kind of capabilities that hosting the DLR’s ScriptRuntime in your app gives you. Currently, IronPython, IronRuby, and JavaScript are supported as hosted scripting languages.

The ScriptRuntime enables you to execute snippets of code or a complete script stored in a file. You can select the proper language engine or allow the DLR to figure out which engine to use. The script can be created in its own app domain or in the current one. Not only can you pass values in and out of the script, you can call methods on dynamic objects created in the script.

This degree of flexibility provides countless uses for hosting the ScriptRuntime. The following example demonstrates one way that you can use the ScriptRuntime. Imagine a shopping cart application. One of the requirements is to calculate a discount based on certain criteria. These discounts change often as new sales campaigns are started and completed. There are many ways to handle such a requirement; this example shows how it could be done using the ScriptRuntime and a little Python scripting.

For simplicity, the example is a Windows client app. It could be part of a larger web application or any other application. Figure 12-1 shows a sample screen for the application.

Using the values provided for the number of items and the total cost of the items, the application applies a discount based on which radio button is selected. In a real application, the system would use a slightly more sophisticated technique to determine the discount to apply, but for this example the radio buttons will suffice.

Here is the code that performs the discount:

private void button1_Click(object sender, RoutedEventArgs e)
{
    string scriptToUse;
    if (CostRadioButton.IsChecked.Value)
    {
        scriptToUse = "AmountDisc.py";
    }
    else
    {
        scriptToUse = "CountDisc.py";
    }
    ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
    ScriptEngine pythEng = scriptRuntime.GetEngine("Python");
    ScriptSource source = pythEng.CreateScriptSourceFromFile(scriptToUse);
    ScriptScope scope = pythEng.CreateScope();
    scope.SetVariable("prodCount", Convert.ToInt32(totalItems.Text));
    scope.SetVariable("amt", Convert.ToDecimal(totalAmt.Text));
    source.Execute(scope);
    label5.Content = scope.GetVariable("retAmt").ToString();
}

The first part just determines which script to apply, AmountDisc.py or CountDisc.py. AmountDisc.py does the discount based on the amount of the purchase:

discAmt = .25
retAmt = amt
if amt > 25.00:
  retAmt = amt-(amt*discAmt)

The minimum amount needed for a discount to be applied is $25. If the amount is less than that, then no discount is applied; otherwise, a discount of 25 percent is applied.

ContDisc.py applies the discount based on the number of items purchased:

discCount = 5
discAmt = .1
retAmt = amt
if prodCount > discCount:
  retAmt = amt-(amt*discAmt)

In this Python script, the number of items purchased must be more than 5 for a 10 percent discount to be applied to the total cost.

The next step is getting the ScriptRuntime environment set up. For this, four specific tasks are performed: creating the ScriptRuntime object, setting the proper ScriptEngine, creating the ScriptSource, and creating the ScriptScope.

The ScriptRuntime object is the starting point, or base, for hosting. It contains the global state of the hosting environment. The ScriptRuntime is created using the CreateFromConfiguration static method. This is what the app.config file looks like:

<configuration>
  <configSections>
    <section 
      name="microsoft.scripting" 
      type="Microsoft.Scripting.Hosting.Configuration.Section, 
          Microsoft.Scripting, 
          Version=0.9.6.10, 
          Culture=neutral, 
          PublicKeyToken=null" 
          requirePermission="false" />
  </configSections>
   
  <microsoft.scripting>
    <languages>
      <language 
        names="IronPython;Python;py" 
        extensions=".py" 
        displayName="IronPython 2.6 Alpha" 
        type="IronPython.Runtime.PythonContext, 
              IronPython, 
              Version=2.6.0.1, 
              Culture=neutral, 
              PublicKeyToken=null" />
    </languages>
  </microsoft.scripting>
</configuration>

The code defines a section for “microsoft.scripting” and sets a couple of properties for the IronPython language engine.

Next, you get a reference to the ScriptEngine from the ScriptRuntime. In the example, you specify that you want the Python engine, but the ScriptRuntime would have been able to determine this on its own because of the py extension on the script.

The ScriptEngine does the work of executing the script code. There are several methods for executing scripts from files or from snippets of code. The ScriptEngine also gives you the ScriptSource and ScriptScope.

The ScriptSource object is what gives you access to the script. It represents the source code of the script. With it you can manipulate the source of the script, load it from a disk, parse it line by line, and even compile the script into a CompiledCode object. This is handy if the same script is executed multiple times.

The ScriptScope object is essentially a namespace. To pass a value into or out of a script, you bind a variable to the ScriptScope. In the following example, you call the SetVariable method to pass into the Python script the prodCount variable and the amt variable. These are the values from the totalItems text box and the totalAmt text box. The calculated discount is retrieved from the script by using the GetVariable method. In this example, the retAmt variable has the value you’re looking for.

The CalcTax button illustrates how to call a method on a Python object. The script CalcTax.py is a very simple method that takes an input value, adds 7.5 percent tax, and returns the new value. Here’s what the code looks like:

def CalcTax(amount):
   return amount*1.075

Here is the C# code to call the CalcTax method:

private void button2_Click(object sender, RoutedEventArgs e)
{
    ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
    dynamic calcRate = scriptRuntime.UseFile("CalcTax.py");
    label6.Content = calcRate.CalcTax(Convert.ToDecimal(label5.Content)).ToString();
}

A very simple process — you create the ScriptRuntime object using the same configuration settings as before. calcRate is a ScriptScope object. You defined it as dynamic so you can easily call the CalcTax method. This is an example of the how the dynamic type can make life a little easier.

DYNAMICOBJECT AND EXPANDOOBJECT

What if you want to create your own dynamic object? You have a couple of options for doing that: by deriving from DynamicObject or by using ExpandoObject. Using DynamicObject is a little more work because you have to override a couple of methods. ExpandoObject is a sealed class that is ready to use.

DynamicObject

Consider an object that represents a person. Normally, you would define properties for the first name, middle name, and last name. Now imagine the capability to build that object during runtime, with the system having no prior knowledge of what properties the object may have or what methods the object may support. That’s what having a DynamicObject-based object can provide. There may be very few times when you need this sort of functionality, but until now the C# language had no way of accommodating such a requirement.

First take a look at what the DynamicObject looks like:

class WroxDynamicObject : DynamicObject
{
    Dictionary<string, object> _dynamicData = new Dictionary<string, object>();
   
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        bool success = false;
        result = null;
        if (_dynamicData.ContainsKey(binder.Name))
        {
            result = _dynamicData[binder.Name];
            success = true;
        }
        else
        {
            result = "Property Not Found!";
            success = false;
        }
        return success;
    }
   
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        _dynamicData[binder.Name] = value;
        return true;
    }
   
    public override bool TryInvokeMember(InvokeMemberBinder binder, 
                                          object[] args, 
                                          out object result)
    {
        dynamic method = _dynamicData[binder.Name];
        result = method((DateTime)args[0]);
        return result != null;            
    }
   
}

In this example, you’re overriding three methods: TrySetMember, TryGetMember, and TryInvokeMember.

TrySetMember adds the new method, property, or field to the object. In this case, you store the member information in a Dictionary object. The SetMemberBinder object that is passed into the TrySetMember method contains the Name property, which is used to identify the element in the Dictionary.

The TryGetMember retrieves the object stored in the Dictionary based on the GetMemberBinder Name property.

Here is the code that makes use of the new dynamic object just created:

dynamic wroxDyn = new WroxDynamicObject();
wroxDyn.FirstName = "Bugs";
wroxDyn.LastName = "Bunny";
Console.WriteLine(wroxDyn.GetType());
Console.WriteLine("{0} {1}", wroxDyn.FirstName, wroxDyn.LastName);

It looks simple enough, but where is the call to the methods you overrode? That’s where the .NET Framework helps. DynamicObject handles the binding for you; all you have to do is reference the properties FirstName and LastName as if they were there all the time.

Adding a method is also easily done. You can use the same WroxDynamicObject and add a GetTomorrowDate method to it. It takes a DateTime object and returns a date string representing the next day. Here’s the code:

dynamic wroxDyn = new WroxDynamicObject();
Func<DateTime, string> GetTomorrow = today => today.AddDays(1).ToShortDateString();
wroxDyn.GetTomorrowDate = GetTomorrow;
Console.WriteLine("Tomorrow is {0}", wroxDyn.GetTomorrowDate(DateTime.Now));

You create the delegate GetTomorrow using Func<T, TResult>. The method the delegate represents is the call to AddDays. One day is added to the Date that is passed in, and a string of that date is returned. The delegate is then set to GetTomorrowDate on the wroxDyn object. The last line calls the new method, passing in the current day’s date. Hence the dynamic magic and you have an object with a valid method.

ExpandoObject

ExpandoObject works similarly to the WroxDynamicObject created in the previous section. The difference is that you don’t have to override any methods, as shown in the following code example:

static void DoExpando()
{
    dynamic expObj = new ExpandoObject();
    expObj.FirstName = "Daffy";
    expObj.LastName = "Duck";
    Console.WriteLine(expObj.FirstName + " " + expObj.LastName);
    Func<DateTime, string> GetTomorrow = today => today.AddDays(1).ToShortDateString();
    expObj.GetTomorrowDate = GetTomorrow;
    Console.WriteLine("Tomorrow is {0}", expObj.GetTomorrowDate(DateTime.Now));
   
    expObj.Friends = new List<Person>();
    expObj.Friends.Add(new Person() { FirstName = "Bob", LastName = "Jones" });
    expObj.Friends.Add(new Person() { FirstName = "Robert", LastName = "Jones" });
    expObj.Friends.Add(new Person() { FirstName = "Bobby", LastName = "Jones" });
   
    foreach (Person friend in expObj.Friends)
    {
        Console.WriteLine(friend.FirstName + " " + friend.LastName);
    }
}

Notice that this code is almost identical to what you did earlier. You add a FirstName and LastName property, add a GetTomorrow function, and then do one additional thing — add a collection of Person objects as a property of the object.

At first glance it may seem that this is no different from using the dynamic type, but there are a couple of subtle differences that are important. First, you can’t just create an empty dynamic typed object. The dynamic type has to have something assigned to it. For example, the following code won’t work:

dynamic dynObj;
dynObj.FirstName = "Joe";

As shown in the previous example, this is possible with ExpandoObject.

Second, because the dynamic type has to have something assigned to it, it will report back the type assigned to it if you do a GetType call. For example, if you assign an int, it will report back that it is an int. This won’t happen with ExpandoObject or an object derived from DynamicObject.

If you have to control the addition and access of properties in your dynamic object, then deriving from DynamicObject is your best option. With DynamicObject, you can use several methods to override and control exactly how the object interacts with the runtime. For other cases, using the dynamic type or the ExpandoObject may be appropriate.

Following is another example of using dynamic and ExpandoObject. Assume that the requirement is to develop a general-purpose comma-separated values (CSV) file parsing tool. You won’t know from one execution to another what data will be in the file, only that the values will be comma-separated and that the first line will contain the field names.

First, open the file and read in the stream. A simple helper method can be used to do this:

private StreamReader OpenFile(string fileName)
{
  if(File.Exists(fileName))
  {
    return new StreamReader(fileName);
  }
  return null;
}

This just opens the file and creates a new StreamReader to read the file contents.

Now you want to get the field names. This is easily done by reading in the first line from the file and using the Split function to create a string array of field names:

string[] headerLine = fileStream.ReadLine().Split(','),

Next is the interesting part. You read in the next line from the file, create a string array just like you did with the field names, and start creating your dynamic objects. Here’s what the code looks like:

var retList = new List<dynamic>();
while (fileStream.Peek() > 0)
{
  string[] dataLine = fileStream.ReadLine().Split(','),
  dynamic dynamicEntity = new ExpandoObject();
  for(int i=0;i<headerLine.Length;i++)
  {
    ((IDictionary<string,object>)dynamicEntity).Add(headerLine[i],dataLine[i]);
  }
  retList.Add(dynamicEntity);
}

Once you have the string array of field names and data elements, you create a new ExpandoObject and add the data to it. Notice that you cast the ExpandoObject to a Dictionary object. You use the field name as the key and the data as the value. Then you can add the new object to the retList object you created and return it to the code that called the method.

What makes this nice is you have a section of code that can handle any data you give it. The only requirements in this case are ensuring that the field names are the first line and that everything is comma-separated. This concept could be expanded to other file types or even to a DataReader.

SUMMARY

In this chapter we looked at how the dynamic type can change the way you look at C# programming. Using ExpandoObject in place of multiple objects can reduce the number of lines of code significantly. Also using the DLR and adding scripting languages like Python or Ruby can help building a more polymorphic application that can be changed easily without re-compiling.

Dynamic development is becoming increasingly popular because it enables you to do things that are very difficult in a statically typed language. The dynamic type and the DLR enable C# programmers to make use of some dynamic capabilities.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset