Jan 20, 2010

C# 4.0 - DynamicObject conversions

--==-- Hello world --==--

This is my first post, so lets start with something simple like DynamicObject's implicit and explicit conversions.

--==-- What is DynamicObject --==--

DynamicObject is the base class for specific implementation for your custom dynamic objects. You can implement dynamic property getters and setters, index property, functions, type conversions, binary operations and unary operations on DO. Basically, it can do dynamically everything that a class would.

ExpandoObject, that comes with the .Net framework 4.0, is a general implementation of this. You can read more on ExpandoObject on Alexandra Rusina's post.

--==-- Conversion of custom DynamicObject --==--

Note. You'll need Visual Studio 2010 beta 2 for trying out samples.

Now, there are many ways that the framework will handle type conversions in different situations.
// Class used for testing.
public class MyDynamicObject : DynamicObject
{
    protected string Value { get; set; }
    public MyDynamicObject(string value) { Value = value; }
    // see method implementations below
}

// Let's start by creating an instance of MyDynamicObject.
dynamic myDynamicObject = new MyDynamicObject("1");


// Example #1
int i = myDynamicObject;

This will count as a explicit cast and like all explicit casting with DynamicObjects, it will be converted by TryConvert method. Exception for this rule is that there is an implicit cast, but more on that later.

Sample for handling explicit conversion:
public override bool TryConvert(ConvertBinder binder, out object result)
{
  try
  {
    // Convert value to requested type
    result = Convert.ChangeType(Value, binder.Type);
    // Conversion succeeded.
    return true;
  }
  catch (Exception exception)
  {
    // Conversion failed. 
    result = null;
    return false;
  }
}

If the conversion fails, you will get an RuntimeBinderException with message "Cannot implicitly convert type 'MyDynamicObject' to 'int'".

// Example #2
int i1 = (int)myDynamicObject + 1;
int i2 = 1 + (int)myDynamicObject;

These cases are similar to example #1, as they are also converted using TryConvert, because the casts are explicit.
If the conversion fails, you will get an RuntimeBinderException with message "Cannot convert type 'MyDynamicObject' to 'int'"

// Example #3
int i = myDynamicObject + 1;

When there is no implicit casting on MyDynamicObject, TryBinaryOperation is used:

public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result)
{
  switch (binder.Operation)
  {
    case System.Linq.Expressions.ExpressionType.Add:
      result = int.Parse(Value) + (int)arg;
      return true;
  }
  result = null;
  return false;
}

This is a sample for using + operator on a DynamicObject. This of course only works if you know that the argument (right-hand side of plus operator) is an int.

This works for DynamicObjects that have only a few specialized operations and types. But what if you have multiple types, for example you want to be able to convert the string to multiple different formats with multiple operations (+,-,*,/,%,...)?

Let's see the next example before coming to that.

// Example #4
int i = 1 + myDynamicObject;

This is a very different case then when myDynamicObject was on left-hand side, as int is now the primary operator. This will on default cause "Operator '+' cannot be applied to operands of type 'int' and 'MyDynamicObject'" error.

Runtime will try to find a suitable + operator or implicit conversion on int that can operate on MyDynamicObject, but can't and throws an exception.

TryBinaryOperation on DynamicObject will only support operations that happend when the DynamicObject is on the left-hand side and TryConvert will only work when the conversion is explicitly stated as in example #2.

That just leaves implicit conversions:

public static implicit operator int(MyDynamicObject myDynamicObject) {
  return int.TryParse(myDynamicObject.Value);
}

After adding implicit conversion to int, there is not need to explicitly cast myDynamicObject to int in example #2. Also, example #3 works now differently. Instead of calling TryBinaryOperation, it will call the implicit cast to int operator. This might be a good thing or a bad thing depending on your use case for the DynamicObject.

For this case, this is perfect, as we don't have to implement each operation (+,-,*,/,%,...) separately, but instead use native int operators.

Note that you have to create implicit conversions for each type you want to support.
You can do more general implicit casting when the DO is on the left-hand side using TryBinaryOperation, but right-hand side is not supported.

That's it for now. Next up.. dynamic Linq to CSV.

0 comments:

Post a Comment