Reputation: 304
Consider the following code
var currentType = Type.GetType("Some.Type, Some");
dynamic myDynamic = new System.Dynamic.ExpandoObject();
myDynamic.A = "A";
var objectInCorrectType = ???
How do I cast the dynamic to the currentType?
Upvotes: 16
Views: 16329
Reputation: 2617
I came across this question because I needed to take a class like this:
public class PropertyChange
{
[JsonProperty("name")]
public string PropertyName { get; set; }
[JsonProperty("value")]
public string PropertyValue { get; set; }
[JsonProperty("arrayValue")]
public dynamic[] PropertyArray { get; set; }
}
and convert the PropertyArray
property of an object (deserialized from JSON using the Newtonsoft library) into an array of objects of a specific type where the type can be derived from the PropertyName
.
I wrote this helper class called DynamicCast<>
and decided to post it here in case anyone else in in the same situation as me.
This helper class allows you to write code like this:
public class MyType
{
public string A { get; set; }
}
var myCast = new DynamicCast<MyType>();
dynamic dyn = ExpandoObject();
dyn.A = "Hello";
var myType = myCast.Cast(dyn);
Console.WriteLine(myType.A); // prints 'Hello'
This is an example of how I used it to solve my deserialization problem:
public class JsonTest
{
[JsonProperty("theArray")]
public dynamic[] TheArray { get; set; }
}
var json = "{'theArray':[{'a':'First'},{'a':'Second'}]}";
var jsonTest = JsonConvert.DeserializeObject<JsonTest>(json);
var myCast = new DynamicCast<MyType>();
var myTypes = myCast.Cast(jsonTest.TheArray).ToArray();
Console.WriteLine(myTypes[0].A); // prints 'First'
The wrote the DynamicCast
class based on other answers here. It looks like this:
public class DynamicCast<T> where T: class, new()
{
private Property[] _proprties;
public DynamicCast()
{
_proprties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.GetSetMethod() != null)
.Where(x => x.GetGetMethod() != null)
.Select(p =>
{
var property = new Property
{
PropertyInfo = p,
Name = p.Name
};
foreach (var attribute in p.GetCustomAttributes(false))
{
if (attribute.GetType() == typeof(JsonPropertyAttribute))
{
var jsonProperty = (JsonPropertyAttribute)attribute;
property.Name = jsonProperty.PropertyName;
break;
}
if (attribute.GetType() == typeof(JsonIgnoreAttribute))
{
return null;
}
}
return property;
})
.Where(p => p != null)
.ToArray();
}
public T Cast(IDictionary<string, object> d)
{
var t = new T();
Fill(d, t);
return t;
}
public T Cast(JObject d)
{
var t = new T();
Fill(d, t);
return t;
}
public dynamic Cast(T t)
{
dynamic d = new ExpandoObject();
Fill(t, d);
return d;
}
public IEnumerable<T> Cast(IEnumerable<JObject> da)
{
return da.Select(e => Cast(e));
}
public IEnumerable<T> Cast(IEnumerable<object> da)
{
return da.Select(e =>
{
if (e is JObject) return Cast((JObject)e);
if (e is IDictionary<string, object>) return Cast((IDictionary<string, object>)e);
return null;
});
}
public void Fill(IDictionary<string, object> values, T target)
{
foreach (var property in _proprties)
if (values.TryGetValue(property.Name, out var value))
property.PropertyInfo.SetValue(target, value, null);
}
public void Fill(JObject values, T target)
{
foreach (var property in _proprties)
{
if (values.TryGetValue(property.Name, out var value))
{
if (value is JValue jvalue)
{
var propertyValue = Convert.ChangeType(jvalue.Value, property.PropertyInfo.PropertyType);
property.PropertyInfo.SetValue(target, propertyValue, null);
}
}
}
}
public void Fill(T obj, IDictionary<string, object> target)
{
foreach (var property in _proprties)
target[property.Name] = property.PropertyInfo.GetValue(obj, null);
}
private class Property
{
public PropertyInfo PropertyInfo;
public string Name;
}
}
You can try this out for yourself in .Net Fiddle here: https://dotnetfiddle.net/J1JXgU
Upvotes: 0
Reputation: 64933
dynamic
is duck-typing a variable (i.e. delaying type check to run-time). It still holds a typed object but it's not checked during compile-time.
Thus, since an ExpandoObject
is a type either if you assign it to a typed or dynamic reference, you can't cast or convert an ExpandoObject
to a type just because it shares the same members as the target type.
BTW, since ExpandoObject
implements IDictionary<string, object>
, you can implement some kind of on-the-fly mapping from the ExpandoObject
instance to target type where a member matches as an extension method:
public static class ExpandObjectExtensions
{
public static TObject ToObject<TObject>(this IDictionary<string, object> someSource, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public)
where TObject : class, new ()
{
Contract.Requires(someSource != null);
TObject targetObject = new TObject();
Type targetObjectType = typeof (TObject);
// Go through all bound target object type properties...
foreach (PropertyInfo property in
targetObjectType.GetProperties(bindingFlags))
{
// ...and check that both the target type property name and its type matches
// its counterpart in the ExpandoObject
if (someSource.ContainsKey(property.Name)
&& property.PropertyType == someSource[property.Name].GetType())
{
property.SetValue(targetObject, someSource[property.Name]);
}
}
return targetObject;
}
}
Now, try the following code and it'll work as you expect:
public class A
{
public int Val1 { get; set; }
}
// Somewhere in your app...
dynamic expando = new ExpandoObject();
expando.Val1 = 11;
// Now you got a new instance of A where its Val1 has been set to 11!
A instanceOfA = ((ExpandoObject)expando).ToObject<A>();
Actually, I've based this answer on other Q&A where I could address a similar issue of mapping objects to dictionary and viceversa: Mapping object to dictionary and vice versa.
Upvotes: 9
Reputation: 39085
You can't cast a dynamic object to a specific type, as @Lasse commented.
However, your question mentions "reflection", so I suspect you're looking for a way to simply map property values (i.e. "creating a new X and copying over values, etc." in Lasse's comment):
...
myDynamic.A = "A";
// get settable public properties of the type
var props = currentType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.GetSetMethod() != null);
// create an instance of the type
var obj = Activator.CreateInstance(currentType);
// set property values using reflection
var values = (IDictionary<string,object>)myDynamic;
foreach(var prop in props)
prop.SetValue(obj, values[prop.Name]);
Upvotes: 14