Review
- Generic type definition
T
is redundant.
- Looping the
ExpandoObject
is better optimized then looping the destination's properties, because you get the items as key-value pair in a single pass.
- Fetch only publicly accessible setter properties to avoid nasty exceptions on
property.SetValue
.
- Don't be too strict on matching the source and destination's types. Take advantage of the built-in type converter API
Convert.ChangeType
.
Proposed Alternative
TypeExtension
allows for a changing an instance's type.
public static class TypeExtension
{
public static bool IsNullable(this Type type)
{
type = type ?? throw new ArgumentNullException(nameof(type));
return type.IsGenericType
&& type.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
}
public static bool IsNullAssignable(this Type type)
{
type = type ?? throw new ArgumentNullException(nameof(type));
return type.IsNullable() || !type.IsValueType;
}
public static object ChangeType(this Type type, object instance)
{
type = type ?? throw new ArgumentNullException(nameof(type));
if (instance == null)
{
if (!type.IsNullAssignable())
{
throw new InvalidCastException($"{type.FullName} is not null-assignable");
}
return null;
}
if (type.IsNullable())
{
type = Nullable.GetUnderlyingType(type);
}
return Convert.ChangeType(instance, type);
}
}
Mapper
could be optimized, made more robust and better suited to map between different types.
public static class Mapper
{
public static void Map(ExpandoObject source, object destination)
{
source = source ?? throw new ArgumentNullException(nameof(source));
destination = destination ?? throw new ArgumentNullException(nameof(destination));
string normalizeName(string name) => name.ToLowerInvariant();
IDictionary<string, object> dict = source;
var type = destination.GetType();
var setters = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanWrite && p.GetSetMethod() != null)
.ToDictionary(p => normalizeName(p.Name));
foreach (var item in dict)
{
if (setters.TryGetValue(normalizeName(item.Key), out var setter))
{
var value = setter.PropertyType.ChangeType(item.Value);
setter.SetValue(destination, value);
}
}
}
}
Test Case:
public class Point
{
public int? X { get; set; }
public double Y { get; set; }
}
static void Main(string[] args)
{
dynamic source = new ExpandoObject();
source.X = 0;
source.Y = 0m;
var destination = new Point
{
X = 1,
Y = 1d
};
Mapper.Map(source, destination);
Console.WriteLine(destination.X);
Console.WriteLine(destination.Y);
Console.ReadKey();
}
JObject
s. As seen here. \$\endgroup\$