Created
July 15, 2010 05:06
-
-
Save hugoware/476530 to your computer and use it in GitHub Desktop.
Semi-dynamic object for .NET code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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