Skip to content

Instantly share code, notes, and snippets.

@hugoware
Created July 15, 2010 05:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hugoware/476530 to your computer and use it in GitHub Desktop.
Save hugoware/476530 to your computer and use it in GitHub Desktop.
Semi-dynamic object for .NET code
//More information about this code
//http://somewebguy.wordpress.com/2010/07/15/almost-sorta-real-dynamic-in-net/
//
// Usage:
//
// var item = new Dynamic();
//
// Assign single values
// item.value = "something";
//
// Create entirely new anonymous types
// item.another = new {
// first = "joe",
// last = "brown",
// age = 50
// };
//
// Modify anonymous types
// item.another.first = "mike";
// item.another.age += 10;
//
// Add new types to existing values
// item.another.settings = new {
// color = "orange",
// website = "http://github.com"
// };
//
//
//
/// <summary>
/// A semi-dynamic object that allows dynamic modifications at runtime
/// </summary>
public class Dynamic : DynamicObject, IEnumerable<string> {
#region Constants
private const string REGEX_ANONYMOUS_TYPE = "^(<>f__AnonymousType|VB\\$AnonymousType)";
private const string REGEX_GROUP_TARGET = "target";
private const string REGEX_GROUP_REMAINING = "target";
private const string REGEX_MATCH_NESTED_PROPERTY_PATH = @"(?<" +
REGEX_GROUP_TARGET +
@">[^\.]+)\.(?<" +
REGEX_GROUP_REMAINING +
">.*)";
/// <summary>
/// Null that can be assigned in when creating an anonymous type
/// </summary>
public static readonly object Null = null;
#endregion
#region Private Classes
//class to prevent mistaking a real dictionary with a
//list of names and values within the dynamic type
private class _DynamicDictionary : Dictionary<string, object> { }
#endregion
#region Constructors
/// <summary>
/// Creates a new Dynamic object
/// </summary>
public Dynamic() {
this._Values = new _DynamicDictionary();
}
/// <summary>
/// Creates a new Dynamic from the object provided
/// </summary>
public Dynamic(object value)
: this() { //Good catch Richard
Dynamic._Merge(this, value);
}
#endregion
#region Properties / Fields
//container of the values for this dynamic object
private _DynamicDictionary _Values;
//the previously requested member (used when the name
//of a type isn't immediately available (like invoking methods)
private string _RequestedMember;
//container for a value when there is no way to convert
//it into a _DynamicDictionary
private object _Self;
#endregion
#region Overriding Methods
/// <summary>
/// Handles accessing a member and creating a new one if required
/// </summary>
public override bool TryGetMember(GetMemberBinder binder, out object result) {
this._RequestedMember = binder.Name;
//check for an assigned value
if (this._Self is object) {
result = this._Self;
}
//check the dictionary for a value
else if (!this._Values.TryGetValue(binder.Name, out result)) {
//if nothing, add it now
Dynamic temp = new Dynamic();
this._Values.Add(binder.Name, temp);
result = temp;
}
//always return this worked
return true;
}
/// <summary>
/// Handles assigning values to the correct container
/// </summary>
public override bool TrySetMember(SetMemberBinder binder, object value) {
this._RequestedMember = binder.Name;
//assign or remove the value
if (value == null) {
this._Values.Remove(binder.Name);
}
else {
this._Values[binder.Name] = Dynamic._ParseObject(value);
}
//return this worked
return true;
}
/// <summary>
/// Returns the value of this object (if any)
/// </summary>
public override string ToString() {
return this._Self is object
? this._Self.ToString()
: string.Empty;
}
#endregion
#region Indexing
/// <summary>
/// Returns the value of a container
/// </summary>
public dynamic this[string key] {
get {
//check if this refers to a nested object
Match match = Regex.Match(key, REGEX_MATCH_NESTED_PROPERTY_PATH);
if (match.Success) {
//get the parts to work with
var parts = new {
target = match.Groups[REGEX_GROUP_TARGET].Value,
remaining = match.Groups[REGEX_GROUP_REMAINING].Value
};
//try and get the target object
Dynamic value = this._Values[parts.target] as Dynamic;
return value is Dynamic
? value[parts.remaining]
: new Dynamic();
}
//otherwise, just return whatever it is
else {
return this._Values[key];
}
}
//clear and append the information
set {
this._Values.Remove(key);
this._Values.Add(key, value);
}
}
#endregion
#region Operators
/// <summary>
/// Handles adding values directly to the object via += or +
/// </summary>
public static dynamic operator +(Dynamic container, object obj) {
Dynamic._Merge(container, obj);
return container;
}
#endregion
#region Helper Methods
//checks if this is anonymous and should be parsed again
private static bool _IsAnonymousType(object value) {
if (value == null) { return false; }
//check the best we can if this is anonymous or not
Type type = value.GetType();
return Regex.IsMatch(type.FullName, REGEX_ANONYMOUS_TYPE) &&
type.IsSealed &&
type.IsGenericType &&
type.BaseType.Equals(typeof(object));
}
//picks apart an object and converts it to a dynamic object
private static dynamic _ParseObject(object obj) {
//make sure these aren't existing dynamic values
if (obj is Dynamic) { return obj as Dynamic; }
if (obj is _DynamicDictionary) { return Dynamic._Merge(new Dynamic(), obj as _DynamicDictionary); }
if (!Dynamic._IsAnonymousType(obj)) { return obj; };
//create the container to use
Dynamic container = new Dynamic();
//get each of the members and check for values
Type type = obj.GetType();
foreach (MemberInfo member in type.GetMembers()) {
if (!(member is PropertyInfo || member is FieldInfo)) { continue; }
//get the values
string name = member.Name;
object value = member is PropertyInfo ? (member as PropertyInfo).GetValue(obj, null)
: member is FieldInfo ? (member as FieldInfo).GetValue(obj)
: null;
//convert this type if needed
if (Dynamic._IsAnonymousType(value)) {
value = Dynamic._ParseObject(value);
}
//add the items for this object
container._Values[name] = value;
}
//return the merged object
return container;
}
//merges two objects together
private static Dynamic _Merge(Dynamic container, object obj) {
//get the value information
if (Dynamic._IsAnonymousType(obj)) {
obj = Dynamic._ParseObject(obj);
}
//merge using
if (obj is Dynamic) {
container._Self = null;
return Dynamic._Merge(container, (obj as Dynamic)._Values);
}
else if (obj is _DynamicDictionary) {
container._Self = null;
return Dynamic._Merge(container, obj as _DynamicDictionary);
}
else {
container._Self = obj;
}
//return the final container to use
return container;
}
//merges a dictionary into another Dynamic
private static Dynamic _Merge(Dynamic container, _DynamicDictionary values) {
foreach (var item in values) {
container._Values.Remove(item.Key);
if (item.Value != null) {
container._Values[item.Key] = item.Value;
}
}
return container;
}
#endregion
#region Static Creation
/// <summary>
/// Creates an empty Dynamic
/// </summary>
public static dynamic Create() {
return new Dynamic();
}
/// <summary>
/// Creates a new Dynamic from the object provided
/// </summary>
public static dynamic Create(object obj) {
return Dynamic._ParseObject(obj);
}
#endregion
#region IEnumerable Members
/// <summary>
/// Gets the names of the keys and values
/// </summary>
public IEnumerator<string> GetEnumerator() {
return this._Values.Keys.GetEnumerator();
}
/// <summary>
/// Gets the names of the keys and values
/// </summary>
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment