Blogs

Paul Kimmel's Blog

Dynamic Objects in C# (against an XML Document)

     

The great thing about programming is there is always something new to learn. There is always something new to learn about DevExpress controls and components and always something new to learn about languages. I try to mix it up, so when you come to this blog you can learn something about DevExpress or something about .NET programming. I am a VB MVP—they asked first—so I try to throw in C# and VB examples.

New in Visual Studio 2010 for VB and C# is DynamicObject(s). A DynamicObject is a type you inherit from that uses late binding against something like a text file, string, array, or an XML document. You define the DynamicObject child class and use member-of syntax (dynamicobject.member) and the member is essentially looked up and bound as if you had defined a custom object. This is a pretty neat feature and it will be interesting to see if it creeps its way into our DevExpress code base and how. (Maybe the first place it will end up in is as CodeRush templates.)

To define a DynamicObject you inherit from System.Dynamic.DynamicObject and override one or more of that class’ methods. For instance, if you override DynamicObject.TryGetMember then the first GetMemberBinder argument will contain the name of the member you want to dynamically bind to. Use TryGetMember out parameter to set the value associated with that member and Boolean return value to indicate that the member value was set. Listing 1 contains a DynamicObject that is initialized with an XML document (containing the cells that describe a Scrabble board but could be anything). When TryGetMember is called and the GetMemberBinder.Name is “Cells” an IList containing the cell XElements is returned.

Listing 1: A DynamicObject that let’s you invoke ScrabbleObjects.Cells to return an IList of XElement objects.

public class ScrabbleObjects : DynamicObject
{
   private string path = "";
   private XDocument document = null;
   private IList list = null;

   public ScrabbleObjects(string path)
   {

     if(!File.Exists(path)) throw new FileNotFoundException();
     this.path = path;
     document = XDocument.Load(path);
     list = (from elem in document.Element("Cells").Elements("Cell")
                 select elem).ToList();
   }

   public override bool TryGetMember(GetMemberBinder binder, out object result)
   {
     result = null;
     if(binder.Name == "Cells")
     {
       result = list;
       return true;
     }
     return false;
   }

}

With just Listing 1 you will still be working with XML—via XElements. Where want to get to is to be able to treat an XElement like a Cell object. Listing 2 defines a second DynamicObject Cell that let’s you treat the XElement like a Cell object, accessing the Row, Column, Score, Text, and BackColor by writing Cell.Row for instance. Listing 2 contains the DynamicObject Cell. The elided XML document is shown in Listing 3.

Listing 2: A DynamicObject Cell that let’s you access XElements by writing code like Cell.Column.

public class Cell : DynamicObject
{
   private XElement element = null;

   public Cell(XElement element)
   {
     this.element = element;
   }

   public override bool  TryGetMember(GetMemberBinder binder, out object result)
   {
     result = null;
     if(binder.Name == "Row")
     {
       result = Convert.ToInt32(element.Attribute("Row").Value);
       return true;
     }
     else if(binder.Name == "Column")
     {
       result = Convert.ToInt32(element.Attribute("Column").Value);
       return true;
     }
     else if(binder.Name == "Score")
     {
       result = Convert.ToInt32(element.Attribute("Score").Value);
       return true;
     }
     else if(binder.Name == "Text")
     {
       result = element.Attribute("Text").Value;
       return true;
     }
     else if(binder.Name == "BackColor")
     {
       result = Color.FromName(element.Attribute("BackColor").Value);
       return true;
     }

     return false;
   }

   public override string ToString()
   {
     const string mask = "Row: {0}, Column: {1}, Score: {2}, Text: {3}, BackColor: {4}";
     dynamic cell = this;
     return string.Format(mask, cell.Row, cell.Column, cell.Score, cell.Text, cell.BackColor);
   }
}

Listing 3: The ellided XML document containing cell definitions. (It doesn’t really matter what the XML contain just map the DynamicObject to the XElements or Attributes.

<?xml version="1.0" encoding="utf-8" ?>
<Cells>
  <Cell Row="1" Column="1" Score="3" Text="TW" BackColor="Orange"></Cell>
  <Cell Row="1" Column="2" Score="1" Text="" BackColor="White"></Cell>
  <Cell Row="1" Column="3" Score="1" Text="" BackColor="White"></Cell>
  <Cell Row="1" Column="4" Score="2" Text="DL" BackColor="Blue"></Cell>
  <Cell Row="1" Column="5" Score="1" Text="" BackColor="White"></Cell>
  <Cell Row="1" Column="6" Score="1" Text="" BackColor="White"></Cell>
  <Cell Row="1" Column="7" Score="1" Text="" BackColor="White"></Cell>
  <Cell Row="1" Column="8" Score="3" Text="TW" BackColor="Orange"></Cell>
  <Cell Row="1" Column="9" Score="1" Text="" BackColor="White"></Cell>
  <Cell Row="1" Column="10" Score="1" Text="" BackColor="White"></Cell>
  <Cell Row="1" Column="11" Score="1" Text="" BackColor="White"></Cell>
  <Cell Row="1" Column="12" Score="2" Text="DL" BackColor="Blue"></Cell>

  <!—remaining cell definitions here -->

</Cells>

ScrabbleObject is initialized with an XML document. Cell is initialized with an XElement. Listing 4 demonstrates how you can initialize the ScrabbleObject class and use the XElements to initialize Cell classes treating Cell like any old instance of a custom class. (All of the code is supplied together in Listing 4.)

Listing 4: Initialize ScrabbleObject with the XML document. (The trick to defining a late bound dynamic object is to declare it with the dynamic keyword.)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Dynamic;
using System.Xml.Linq;
using System.IO;
using System.Collections;
using System.Drawing;

namespace XmlAsDynamicObjects
{
  class Program
  {
    static void Main(string[] args)
    {
      dynamic layout = new ScrabbleObjects("..\\..\\BasicLayout.xml");

      foreach (dynamic cell in layout.Cells)
      {
        Console.WriteLine(cell);
      }
      Console.ReadLine();
    }
  }

  public class ScrabbleObjects : DynamicObject
  {
    private string path = "";
    private XDocument document = null;
    private IList list = null;

    public ScrabbleObjects(string path)
    {

      if(!File.Exists(path)) throw new FileNotFoundException();
      this.path = path;
      document = XDocument.Load(path);
      list = (from elem in document.Element("Cells").Elements("Cell")
                  select elem).ToList();
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
      result = null;
      if(binder.Name == "Cells")
      {
        result = list;
        return true;
      }
      return false;
    }

  }

  public class Cell : DynamicObject
  {
    private XElement element = null;

    public Cell(XElement element)
    {
      this.element = element;
    }

    public override bool  TryGetMember(GetMemberBinder binder, out object result)
    {
      result = null;
      if(binder.Name == "Row")
      {
        result = Convert.ToInt32(element.Attribute("Row").Value);
        return true;
      }
      else if(binder.Name == "Column")
      {
        result = Convert.ToInt32(element.Attribute("Column").Value);
        return true;
      }
      else if(binder.Name == "Score")
      {
        result = Convert.ToInt32(element.Attribute("Score").Value);
        return true;
      }
      else if(binder.Name == "Text")
      {
        result = element.Attribute("Text").Value;
        return true;
      }
      else if(binder.Name == "BackColor")
      {
        result = Color.FromName(element.Attribute("BackColor").Value);
        return true;
      }

      return false;
    }

    public override string ToString()
    {
      const string mask = "Row: {0}, Column: {1}, Score: {2}, Text: {3}, BackColor: {4}";
      dynamic cell = this;
      return string.Format(mask, cell.Row, cell.Column, cell.Score, cell.Text, cell.BackColor);
    }
  }
}

The new keyword dynamic tells the compiler that layout is a late bound object. The layout object is assigned to an instance of ScrabbleObjects. The foreach statement uses dynamic again to implicitly create new Cell objects from the IList Cells—equivalent to writing dynamic cell = new Cell(XElement). Inherit from DynamicObject, override the base classes key member(s), and declare instances using the new dynamic keyword. That’s pretty much all there is to it.

It will be interesting to see how developers use (or abuse DynamicObject). It will be interesting to see how it crops up with DevExpress code, and it will be interesting what caveats crop up as people figure out how to best use these VS2010 feature.

Published Mar 17 2010, 03:45 PM by Paul Kimmel (DevExpress)
Bookmark and Share

Comments

 

Chris Royle said:

It'll be interesting to see if Dynamic Objects can be supported within XAF.

March 20, 2010 6:54 AM
 

Paul Kimmel's Blog said:

Getting new software, tools, and the books that support them always feel a little like Christmas to me

April 14, 2010 4:25 PM
More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.