I've recently answered a question here.
public class TypeAccessor<T>
{
private readonly Func<T, T> m_applyDefaultValues;
private readonly Func<T> m_constructType;
public ReadOnlyCollection<string> CloneableProperties { get; }
public ReadOnlyDictionary<string, Func<T, object>> GetterCache { get; }
public ReadOnlyDictionary<string, Action<T, object>> SetterCache { get; }
public TypeAccessor(T defaultValue, bool includeNonPublic = false)
{
PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Instance |
(includeNonPublic
? BindingFlags.NonPublic
: BindingFlags.Default) |
BindingFlags.Public);
GetterCache = new ReadOnlyDictionary<string, Func<T, object>>(properties.Select(propertyInfo => new
{
PropertyName = propertyInfo.Name,
PropertyGetAccessor = propertyInfo.GetGetAccessor<T>(includeNonPublic)
}).Where(a => a.PropertyGetAccessor != null)
.ToDictionary(a => a.PropertyName, a => a.PropertyGetAccessor));
SetterCache = new ReadOnlyDictionary<string, Action<T, object>>(properties.Select(propertyInfo => new
{
PropertyName = propertyInfo.Name,
PropertySetAccessor = propertyInfo.GetSetAccessor<T>(includeNonPublic)
}).Where(a => a.PropertySetAccessor != null)
.ToDictionary(a => a.PropertyName, a => a.PropertySetAccessor));
CloneableProperties = Array.AsReadOnly(GetterCache.Keys.Intersect(SetterCache.Keys).ToArray());
if (typeof(T).IsValueType)
{
m_applyDefaultValues = instance => defaultValue;
m_constructType = () => defaultValue;
}
else if (defaultValue != null)
{
var defaultConstructor = GetDefaultConstructor();
var propertyValues = GetProperties(defaultValue, CloneableProperties).ToArray();
m_applyDefaultValues = instance =>
{
SetProperties(instance, propertyValues);
return instance;
};
m_constructType = () => m_applyDefaultValues(defaultConstructor());
}
else
{
m_applyDefaultValues = instance => default(T);
m_constructType = () => default(T);
}
}
public void CloneProperties(T source, T target)
{
SetProperties(target, GetProperties(source, CloneableProperties));
}
public void SetToDefault(ref T instance)
{
instance = m_applyDefaultValues(instance);
}
public T New()
{
return m_constructType();
}
private Dictionary<string, object> GetProperties(T instance, IEnumerable<string> properties)
=> properties?.ToDictionary(propertyName => propertyName,
propertyName => GetProperty(instance, propertyName)) ?? new Dictionary<string, object>();
public Dictionary<string, object> GetProperties(T instance,
IEnumerable<Expression<Func<T, object>>> properties)
=> properties?.ToDictionary(property => GetMemberInfo(property).Name,
property => GetProperty(instance, property)) ?? new Dictionary<string, object>();
public Dictionary<string, object> GetProperties(T instance)
=> GetterCache.Keys.ToDictionary(key => key, key => GetProperty(instance, key));
private object GetProperty(T instance, string propertyName)
=> GetterCache[propertyName].Invoke(instance);
public TValue GetProperty<TValue>(T instance, Expression<Func<T, TValue>> property)
=> (TValue) GetterCache[GetMemberInfo(property).Name](instance);
private void SetProperty(T instance, string propertyName, object value)
{
Action<T, object> setter;
if (SetterCache.TryGetValue(propertyName, out setter))
{
setter(instance, value);
}
else
{
throw new KeyNotFoundException(
$"a property setter with the name does not {propertyName} exist on {typeof(T).FullName}");
}
}
public void SetProperty<TValue>(T instance, Expression<Func<T, TValue>> property, TValue value)
=> SetterCache[GetMemberInfo(property).Name](instance, value);
private void SetProperties<TValue>(T instance, IEnumerable<KeyValuePair<string, TValue>> properties)
{
if (properties != null)
{
foreach (var property in properties)
{
SetProperty(instance, property.Key, property.Value);
}
}
}
public void SetProperties<TValue>(T instance,
IEnumerable<KeyValuePair<Expression<Func<T, TValue>>, TValue>> propertiesInfo)
{
foreach (var propertyInfo in propertiesInfo)
{
SetterCache[GetMemberInfo(propertyInfo.Key).Name](instance, propertyInfo.Value);
}
}
private MemberInfo GetMemberInfo(Expression expression)
{
LambdaExpression lambda = (LambdaExpression) expression;
MemberExpression memberExpr = null;
switch (lambda.Body.NodeType)
{
case ExpressionType.Convert:
memberExpr =
((UnaryExpression) lambda.Body).Operand as MemberExpression;
break;
case ExpressionType.MemberAccess:
memberExpr = lambda.Body as MemberExpression;
break;
}
return memberExpr.Member;
}
private static Func<T> GetDefaultConstructor()
{
var type = typeof(T);
if (type == typeof(string))
{
return
Expression.Lambda<Func<T>>(Expression.TypeAs(Expression.Constant(null), typeof(string))).Compile();
}
if (type.HasDefaultConstructor())
{
return Expression.Lambda<Func<T>>(Expression.New(type)).Compile();
}
return () => (T) FormatterServices.GetUninitializedObject(type);
}
}
These are the extension classes:
public static class TypeAccessor
{
/// <summary>
/// Creates a new instance of the <see cref="TypeAccessor{_}"/> class using the specified <see cref="T"/>.
/// </summary>
public static TypeAccessor<T> Create<T>(T instance, bool includeNonPublic = false)
{
return new TypeAccessor<T>(instance, includeNonPublic);
}
}
public static class TypeExtensions
{
public static bool HasDefaultConstructor(this Type type)
{
return (type.IsValueType || (type.GetConstructor(Type.EmptyTypes) != null));
}
}
public static class PropertyInfoExtensions
{
/// <summary>
/// Generates an <see cref="Expression{Func{_,_}}"/> that represents the current <see cref="PropertyInfo"/>'s getter.
/// </summary>
public static Expression<Func<TSource, TProperty>> GetGetAccessor<TSource, TProperty>(
this PropertyInfo propertyInfo, bool includeNonPublic = false)
{
var getMethod = propertyInfo.GetGetMethod(includeNonPublic);
if (getMethod != null && propertyInfo.GetIndexParameters().Length == 0)
{
var instance = Expression.Parameter(typeof(TSource), "instance");
var value = Expression.Call(instance, getMethod);
return Expression.Lambda<Func<TSource, TProperty>>(
propertyInfo.PropertyType.IsValueType
? Expression.Convert(value, typeof(TProperty))
: Expression.TypeAs(value, typeof(TProperty)),
instance
);
}
else
{
return null;
}
}
/// <summary>
/// Generates a <see cref="Func{_,_}"/> delegate to the current <see cref="PropertyInfo"/>'s getter.
/// </summary>
/// <param name="includeNonPublic">Indicates whether a non-public get accessor should be returned.</param>
public static Func<TSource, object> GetGetAccessor<TSource>(this PropertyInfo propertyInfo,
bool includeNonPublic = false)
{
return propertyInfo.GetGetAccessor<TSource, object>(includeNonPublic)?.Compile();
}
/// <summary>
/// Generates an <see cref="Expression{Action{_,_}};"/> that represents the current <see cref="PropertyInfo"/>'s setter.
/// </summary>
/// <param name="includeNonPublic">Indicates whether a non-public set accessor should be returned.</param>
public static Expression<Action<TSource, TProperty>> GetSetAccessor<TSource, TProperty>(
this PropertyInfo propertyInfo, bool includeNonPublic = false)
{
var setMethod = propertyInfo.GetSetMethod(includeNonPublic);
if (setMethod != null && propertyInfo.GetIndexParameters().Length == 0)
{
var instance = Expression.Parameter(typeof(TSource), "instance");
var value = Expression.Parameter(typeof(TProperty), "value");
return Expression.Lambda<Action<TSource, TProperty>>(
Expression.Call(
instance,
setMethod,
propertyInfo.PropertyType.IsValueType
? Expression.Convert(value, propertyInfo.PropertyType)
: Expression.TypeAs(value, propertyInfo.PropertyType)
),
instance,
value
);
}
else
{
return null;
}
}
/// <summary>
/// Generates an <see cref="Action{_,_}"/> delegate to the current <see cref="PropertyInfo"/>'s setter.
/// </summary>
/// <param name="includeNonPublic">Indicates whether a non-public set accessor should be returned.</param>
public static Action<TSource, object> GetSetAccessor<TSource>(this PropertyInfo propertyInfo,
bool includeNonPublic = false)
{
return propertyInfo.GetSetAccessor<TSource, object>(includeNonPublic)?.Compile();
}
}
Here's a usage example:
public class Point2D
{
public double X { get; set; }
public double Y { get; set; }
}
var pointA = new Point2D {X = 9000.01, Y = 0.0};
var accessor = TypeAccessor.Create(pointA);
var pointB = new Point2D();
//obtains properties by name with compile time safety
Dictionary<string, object> a = accessor.GetProperties(pointA, new List<Expression<Func<Point2D, object>>>
{
d => d.X,
d => d.Y
});
accessor.CloneProperties(pointA, pointB); // pointB.X should now be 9000.01
accessor.SetProperty(pointA, p => p.X, 0.0); // sets pointA.X to 0.0
Console.WriteLine(accessor.GetProperty(pointA, p => p.X)); // prints pointA.X, should be 0.0
accessor.SetToDefault(ref pointA); // sets pointA's properties to default the accessor's default values
Console.WriteLine(accessor.GetProperty(pointA, p => p.X)); // prints pointA.X, should be 9000.01
It uses both a private method which is not compile-time typesafe and a public one which is compile-time typesafe, so the user won't be able to mess up his naming. But inside the class, we can't know what the type argument T is, so we still need to use some methods which work with strings as parameters instead of Expressions.