I love dynamic functionality in a strongly typed language because it offers us the best of both worlds. In C# (or any of the main .NET languages) we now have the dynamic type that provides a host of dynamic features for the static C# language. One place where I've found dynamic
to be incredibly useful is in building extensible types or types that expose traditionally non-object data (like dictionaries) in easier
to use and more readable syntax. I wrote about a couple of these for accessing old school ADO.NET DataRows and DataReaders more easily for example. These classes are dynamic wrappers that provide easier syntax and auto-type conversions which greatly simplifies code clutter and increases clarity in existing code. ExpandoObject in .NET 4.0 Another great use case for dynamic objects is the ability
to create extensible objects - objects that start out with a set of static members and then can add additional properties and even methods dynamically. The .NET 4.0 framework actually includes an ExpandoObject class which provides a very dynamic
object that allows you
to add properties and methods on the fly and then access them again. For example with ExpandoObject you can do stuff like this:dynamic expand = new ExpandoObject();
expand.Name = "Rick";
expand.HelloWorld = (Func<string, string>) ((string name) =>
{
return "Hello " + name;
});
Console.WriteLine(expand.Name);
Console.WriteLine(expand.HelloWorld("Dufus"));
Internally ExpandoObject uses a
Dictionary like structure and interface
to store properties and methods and then allows you
to add and access properties and methods easily. As cool as ExpandoObject is it has a few shortcomings too:
It's a sealed type so you can't use it as a base class
It only works off 'properties' in the internal
Dictionary - you can't expose existing type data
It doesn't serialize
to XML or with DataContractSerializer/DataContractJsonSerializer
Expando - A truly extensible
Object
ExpandoObject is nice if you just need a dynamic container for a
dictionary like structure. However, if you want
to build an extensible
object that starts out with a set of strongly typed properties and then allows you
to extend it, ExpandoObject does not work because it's a sealed class that can't be inherited.
I started thinking about this very scenario for one of my applications I'm building for a customer. In this system we are connecting
to various different user stores. Each user store has the same basic requirements for username, password, name etc. But then each store also has a number of extended properties that is available
to each application. In the real world scenario the data is loaded from the database in a data reader and the known properties are assigned from the known fields in the database. All unknown fields are then 'added'
to the expando
object dynamically.
In the past I've done this very thing with a separate property - Properties - just like I do for this class. But the property and
dictionary syntax is not ideal and tedious
to work with.
I started thinking about how
to represent these extra property structures. One way certainly would be
to add a
Dictionary, or an ExpandoObject
to hold all those extra properties. But wouldn't it be nice if the application could actually extend an existing
object that looks something like this as you can with the Expando object:public class User : Westwind.Utilities.Dynamic.Expando
{
public string Email { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public DateTime? ExpiresOn { get; set; }
}
and then simply start extending the properties of this
object dynamically? Using the Expando
object I describe later you can now do the following:[TestMethod]
public void UserExampleTest()
{
var user = new User();
// Set strongly typed properties
user.Email = "
[email protected]";
user.Password = "nonya123";
user.Name = "Rickochet";
user.Active = true;
// Now add dynamic properties
dynamic duser = user;
duser.Entered = DateTime.Now;
duser.Accesses = 1;
// you can also add dynamic props via indexer
user["NickName"] = "AntiSocialX";
duser["WebSite"] = "http://www.west-wind.com/weblog";
// Access strong type through dynamic ref
Assert.AreEqual(user.Name,duser.Name);
// Access strong type through indexer
Assert.AreEqual(user.Password,user["Password"]);
// access dyanmically added value through indexer
Assert.AreEqual(duser.Entered,user["Entered"]);
// access index added value through dynamic
Assert.AreEqual(user["NickName"],duser.NickName);
// loop through all properties dynamic AND strong type properties (true)
foreach (var prop in user.GetProperties(true))
{
object val = prop.Value;
if (val == null)
val = "null";
Console.WriteLine(prop.Key + ": " + val.ToString());
}
}
As you can see this code somewhat blurs the line between a static and dynamic type. You start with a strongly typed
object that has a fixed set of properties. You can then cast the
object to dynamic (as I discussed in my last post) and add additional properties
to the
object. You can also use an indexer
to add dynamic properties
to the
object.
To access the strongly typed properties you can use either the strongly typed instance, the indexer or the dynamic cast of the
object. Personally I think it's kinda cool
to have an easy way
to access strongly typed properties by string which can make some data scenarios much easier.
To access the 'dynamically added' properties you can use either the indexer on the strongly typed
object, or property syntax on the dynamic cast.
Using the dynamic type allows all three modes
to work on both strongly typed and dynamic properties.
Finally you can iterate over all properties, both dynamic and strongly typed if you chose. Lots of flexibility.
Note also that by default the Expando
object works against the (this) instance meaning it extends the current
object. You can also pass in a separate instance
to the constructor in which case that
object will be used
to iterate over
to find properties rather than this.
Using this approach provides some really interesting functionality when use the dynamic type.
To use this we have
to add an explicit constructor
to the Expando subclass:public class User : Westwind.Utilities.Dynamic.Expando
{
public string Email { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public DateTime? ExpiresOn { get; set; }
public User() : base()
{ }
// only required if you want
to mix in seperate instance
public User(object instance)
: base(instance)
{
}
}
to allow the instance
to be passed. When you do you can now do:[TestMethod]
public void ExpandoMixinTest()
{
// have Expando work on Addresses
var user = new User( new Address() );
// cast
to dynamicAccessToPropertyTest
dynamic duser = user;
// Set strongly typed properties
duser.Email = "
[email protected]";
user.Password = "nonya123";
// Set properties on address
object
duser.Address = "32 Kaiea";
//duser.Phone = "808-123-2131";
// set dynamic properties
duser.NonExistantProperty = "This works too";
// shows default value Address.Phone value
Console.WriteLine(duser.Phone);
}
Using the dynamic cast in this case allows you
to access *three* different 'objects': The strong type properties, the dynamically added properties in the
dictionary and the properties of the instance passed in! Effectively this gives you a way
to simulate multiple inheritance (which is scary - so be very careful with this, but you can do it).
How Expando works
Behind the scenes Expando is a DynamicObject subclass as I discussed in my last post. By implementing a few of DynamicObject's methods you can basically create a type that can trap 'property missing' and 'method missing' operations. When you access a non-existant property a known method is fired that our code can intercept and provide a value for. Internally Expando uses a custom
dictionary implementation
to hold the dynamic properties you might add
to your expandable
object.
Let's look at code first. The code for the Expando type is straight forward and given what it provides relatively short. Here it is.using System;
using System.Collections.Generic;
using System.Linq;
using System.Dynamic;
using System.Reflection;
namespace Westwind.Utilities.Dynamic
{
/// <summary>
/// Class that provides extensible properties and methods. This
/// dynamic
object stores 'extra' properties in a
dictionary or
/// checks the actual properties of the instance.
///
/// This means you can subclass this expando and retrieve either
/// native properties or properties from values in the
dictionary.
///
/// This type allows you three ways
to access its properties:
///
/// Directly: any explicitly declared properties are accessible
/// Dynamic: dynamic cast allows access
to dictionary and native properties/methods
/// Dictionary: Any of the extended properties are accessible via IDictionary interface
/// </summary>
[Serializable]
public class Expando : DynamicObject, IDynamicMetaObjectProvider
{
/// <summary>
/// Instance of
object passed in
/// </summary>
object Instance;
/// <summary>
/// Cached type of the instance
/// </summary>
Type InstanceType;
PropertyInfo[] InstancePropertyInfo
{
get
{
if (_InstancePropertyInfo == null && Instance != null)
_InstancePropertyInfo = Instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
return _InstancePropertyInfo;
}
}
PropertyInfo[] _InstancePropertyInfo;
/// <summary>
/// String
Dictionary that contains the extra dynamic values
/// stored on this object/instance
/// </summary>
/// <remarks>Using PropertyBag
to support XML Serialization of the dictionary</remarks>
public PropertyBag Properties = new PropertyBag();
//public Dictionary<string,object> Properties = new Dictionary<string, object>();
/// <summary>
/// This constructor just works off the internal
dictionary and any
/// public properties of this
object.
///
/// Note you can subclass Expando.
/// </summary>
public Expando()
{
Initialize(this);
}
/// <summary>
/// Allows passing in an existing instance variable
to 'extend'.
/// </summary>
/// <remarks>
/// You can pass in null here if you don't want
to
/// check native properties and only check the Dictionary!
/// </remarks>
/// <param name="instance"></param>
public Expando(object instance)
{
Initialize(instance);
}
protected virtual void Initialize(object instance)
{
Instance = instance;
if (instance != null)
InstanceType = instance.GetType();
}
/// <summary>
/// Try
to retrieve a member by name first from instance properties
/// followed by the collection entries.
/// </summary>
/// <param name="binder"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool TryGetMember(GetMemberBinder binder, out
object result)
{
result = null;
// first check the Properties collection for member
if (Properties.Keys.Contains(binder.Name))
{
result = Properties[binder.Name];
return true;
}
// Next check for Public properties via Reflection
if (Instance != null)
{
try
{
return GetProperty(Instance, binder.Name, out result);
}
catch { }
}
// failed
to retrieve a property
result = null;
return false;
}
/// <summary>
/// Property setter implementation tries
to retrieve value from instance
/// first then into this
object
/// </summary>
/// <param name="binder"></param>
/// <param name="value"></param>
/// <returns></returns>
public override bool TrySetMember(SetMemberBinder binder,
object value)
{
// first check
to see if there's a native property
to set
if (Instance != null)
{
try
{
bool result = SetProperty(Instance, binder.Name, value);
if (result)
return true;
}
catch { }
}
// no match - set or add
to dictionary
Properties[binder.Name] = value;
return true;
}
/// <summary>
/// Dynamic invocation method. Currently allows only for Reflection based
/// operation (no ability
to add methods dynamically).
/// </summary>
/// <param name="binder"></param>
/// <param name="args"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out
object result)
{
if (Instance != null)
{
try
{
// check instance passed in for methods
to invoke
if (InvokeMethod(Instance, binder.Name, args, out result))
return true;
}
catch { }
}
result = null;
return false;
}
/// <summary>
/// Reflection Helper method
to retrieve a property
/// </summary>
/// <param name="instance"></param>
/// <param name="name"></param>
/// <param name="result"></param>
/// <returns></returns>
protected bool GetProperty(object instance, string name, out
object result)
{
if (instance == null)
instance = this;
var miArray = InstanceType.GetMember(name, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance);
if (miArray != null && miArray.Length > 0)
{
var mi = miArray[0];
if (mi.MemberType == MemberTypes.Property)
{
result = ((PropertyInfo)mi).GetValue(instance,null);
return true;
}
}
result = null;
return false;
}
/// <summary>
/// Reflection helper method
to set a property value
/// </summary>
/// <param name="instance"></param>
/// <param name="name"></param>
/// <param name="value"></param>
/// <returns></returns>
protected bool SetProperty(object instance, string name,
object value)
{
if (instance == null)
instance = this;
var miArray = InstanceType.GetMember(name, BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.Instance);
if (miArray != null && miArray.Length > 0)
{
var mi = miArray[0];
if (mi.MemberType == MemberTypes.Property)
{
((PropertyInfo)mi).SetValue(Instance, value, null);
return true;
}
}
return false;
}
/// <summary>
/// Reflection helper method
to invoke a method
/// </summary>
/// <param name="instance"></param>
/// <param name="name"></param>
/// <param name="args"></param>
/// <param name="result"></param>
/// <returns></returns>
protected bool InvokeMethod(object instance, string name, object[] args, out
object result)
{
if (instance == null)
instance = this;
// Look at the instanceType
var miArray = InstanceType.GetMember(name,
BindingFlags.InvokeMethod |
BindingFlags.Public | BindingFlags.Instance);
if (miArray != null && miArray.Length > 0)
{
var mi = miArray[0] as MethodInfo;
result = mi.Invoke(Instance, args);
return true;
}
result = null;
return false;
}
/// <summary>
/// Convenience method that provides a string Indexer
///
to the Properties collection AND the strongly typed
/// properties of the
object by name.
///
/// // dynamic
/// exp["Address"] = "112 nowhere lane";
/// // strong
/// var name = exp["StronglyTypedProperty"] as string;
/// </summary>
/// <remarks>
/// The getter checks the Properties
dictionary first
/// then looks in PropertyInfo for properties.
/// The setter checks the instance properties before
/// checking the Properties
dictionary.
/// </remarks>
/// <param name="key"></param>
///
/// <returns></returns>
public
object this[string key]
{
get
{
try
{
// try
to get from properties collection first
return Properties[key];
}
catch (KeyNotFoundException ex)
{
// try reflection on instanceType
object result = null;
if (GetProperty(Instance, key, out result))
return result;
// nope doesn't exist
throw;
}
}
set
{
if (Properties.ContainsKey(key))
{
Properties[key] = value;
return;
}
// check instance for existance of type first
var miArray = InstanceType.GetMember(key, BindingFlags.Public | BindingFlags.GetProperty);
if (miArray != null && miArray.Length > 0)
SetProperty(Instance, key, value);
else
Properties[key] = value;
}
}
/// <summary>
/// Returns and the properties of
/// </summary>
/// <param name="includeProperties"></param>
/// <returns></returns>
public IEnumerable<KeyValuePair<string,object>> GetProperties(bool includeInstanceProperties = false)
{
if (includeInstanceProperties && Instance != null)
{
foreach (var prop in this.InstancePropertyInfo)
yield return new KeyValuePair<string, object>(prop.Name, prop.GetValue(Instance, null));
}
foreach (var key in this.Properties.Keys)
yield return new KeyValuePair<string, object>(key, this.Properties[key]);
}
/// <summary>
/// Checks whether a property exists in the Property collection
/// or as a property on the instance
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool Contains(KeyValuePair<string, object> item, bool includeInstanceProperties = false)
{
bool res = Properties.ContainsKey(item.Key);
if (res)
return true;
if (includeInstanceProperties && Instance != null)
{
foreach (var prop in this.InstancePropertyInfo)
{
if (prop.Name == item.Key)
return true;
}
}
return false;
}
}
}
Although the Expando class supports an indexer, it doesn't actually implement IDictionary or even IEnumerable. It only provides the indexer and Contains() and GetProperties() methods, that work against the Properties
dictionary AND the internal instance.
The reason for not implementing IDictionary is that a) it doesn't add much value since you can access the Properties
dictionary directly and that b) I wanted
to keep the interface
to class very lean so that it can serve as an entity type if desired. Implementing these IDictionary (or even IEnumerable) causes LINQ extension methods
to pop up on the type which obscures the property interface and would only confuse the purpose of the type. IDictionary and IEnumerable are also problematic for XML and JSON Serialization - the XML Serializer doesn't serialize IDictionary<string,object>, nor does the DataContractSerializer. The JavaScriptSerializer does serialize, but it treats the entire
object like a
dictionary and doesn't serialize the strongly typed properties of the type, only the
dictionary values which is also not desirable. Hence the decision
to stick with only implementing the indexer
to support the user["CustomProperty"] functionality and leaving iteration functions
to the publicly exposed Properties
dictionary.
Note that the
Dictionary used here is a custom PropertyBag class I created
to allow for serialization
to work. One important aspect for my apps is that whatever custom properties get added they have
to be accessible
to AJAX clients since the particular app I'm working on is a SIngle Page Web app where most of the Web access is through JSON AJAX calls. PropertyBag can serialize
to XML and one way serialize
to JSON using the JavaScript serializer (not the DCS serializers though).
The key components that make Expando work in this code are the Properties
Dictionary and the TryGetMember() and TrySetMember() methods. The Properties collection is public so if you choose you can explicitly access the collection
to get better performance or
to manipulate the members in internal code (like loading up dynamic values form a database).
Notice that TryGetMember() and TrySetMember() both work against the
dictionary AND the internal instance
to retrieve and set properties. This means that user["Name"] works against native properties of the
object as does user["Name"] = "RogaDugDog".
What's your Use Case?
This is still an early prototype but I've plugged it into one of my customer's applications and so far it's working very well. The key features for me were the ability
to easily extend the type with values coming from a database and exposing those values in a nice and easy
to use manner. I'm also finding that using this type of
object for ViewModels works very well
to add custom properties
to view models. I suspect there will be lots of uses for this - I've been using the extra
dictionary approach
to extensibility for years - using a dynamic type
to make the syntax cleaner is just a bonus here.
What can you think of
to use this for?
Resources
Source Code and Tests (GitHub)
Also integrated in Westwind.Utilities of the West Wind Web Toolkit
West Wind Utilities NuGet© Rick Strahl, West Wind Technologies, 2005-2012Posted in CSharp .NET Dynamic Types
Tweet
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");
(function() {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/plusone.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();