using System;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.Collections.Concurrent;
public static class AnonymousObjectMutator
{
private const BindingFlags FieldFlags = BindingFlags.NonPublic | BindingFlags.Instance;
private const BindingFlags PropFlags = BindingFlags.Public | BindingFlags.Instance;
private static readonly string[] BackingFieldFormats = { "<{0}>i__Field", "<{0}>" };
private static ConcurrentDictionary<Type, IDictionary<string, Action<object, object>>> _map =
new ConcurrentDictionary<Type, IDictionary<string, Action<object, object>>>();
public static T Set<T, TProperty>(
this T instance,
Expression<Func<T, TProperty>> propExpression,
TProperty newValue) where T : class
{
GetSetterFor(propExpression)(instance, newValue);
return instance;
}
private static Action<object, object> GetSetterFor<T, TProperty>(Expression<Func<T, TProperty>> propExpression)
{
var memberExpression = propExpression.Body as MemberExpression;
if (memberExpression == null || memberExpression.Member.MemberType != MemberTypes.Property)
throw new InvalidOperationException("Only property expressions are supported");
Action<object, object> setter = null;
GetPropMap<T>().TryGetValue(memberExpression.Member.Name, out setter);
if (setter == null)
throw new InvalidOperationException("No setter found");
return setter;
}
private static IDictionary<string, Action<object, object>> GetPropMap<T>()
{
return _map.GetOrAdd(typeof(T), x => BuildPropMap<T>());
}
private static IDictionary<string, Action<object, object>> BuildPropMap<T>()
{
var typeMap = new Dictionary<string, Action<object, object>>();
var fields = typeof(T).GetFields(FieldFlags);
foreach (var pi in typeof(T).GetProperties(PropFlags))
{
var backingFieldNames = BackingFieldFormats.Select(x => string.Format(x, pi.Name)).ToList();
var fi = fields.FirstOrDefault(f => backingFieldNames.Contains(f.Name) && f.FieldType == pi.PropertyType);
if (fi == null)
throw new NotSupportedException(string.Format("No backing field found for property {0}.", pi.Name));
typeMap.Add(pi.Name, (inst, val) => fi.SetValue(inst, val));
}
return typeMap;
}
}
public class Program
{
public static void Main()
{
var myAnonInstance = new {
FirstField = "Hello",
AnotherField = 30,
};
Console.WriteLine(myAnonInstance);
myAnonInstance
.Set(x => x.FirstField, "Hello SO")
.Set(x => x.AnotherField, 42);
Console.WriteLine(myAnonInstance);
}
}
dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uTGlucTsKdXNpbmcgU3lzdGVtLlJlZmxlY3Rpb247CnVzaW5nIFN5c3RlbS5MaW5xLkV4cHJlc3Npb25zOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLkNvbGxlY3Rpb25zLkNvbmN1cnJlbnQ7CgpwdWJsaWMgc3RhdGljIGNsYXNzIEFub255bW91c09iamVjdE11dGF0b3IKewogICAgcHJpdmF0ZSBjb25zdCBCaW5kaW5nRmxhZ3MgRmllbGRGbGFncyA9IEJpbmRpbmdGbGFncy5Ob25QdWJsaWMgfCBCaW5kaW5nRmxhZ3MuSW5zdGFuY2U7CiAgICBwcml2YXRlIGNvbnN0IEJpbmRpbmdGbGFncyBQcm9wRmxhZ3MgPSBCaW5kaW5nRmxhZ3MuUHVibGljIHwgQmluZGluZ0ZsYWdzLkluc3RhbmNlOwogICAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgc3RyaW5nW10gQmFja2luZ0ZpZWxkRm9ybWF0cyA9IHsgIjx7MH0+aV9fRmllbGQiLCAiPHswfT4iIH07CiAgICBwcml2YXRlIHN0YXRpYyBDb25jdXJyZW50RGljdGlvbmFyeTxUeXBlLCBJRGljdGlvbmFyeTxzdHJpbmcsIEFjdGlvbjxvYmplY3QsIG9iamVjdD4+PiBfbWFwID0KICAgICAgICBuZXcgQ29uY3VycmVudERpY3Rpb25hcnk8VHlwZSwgSURpY3Rpb25hcnk8c3RyaW5nLCBBY3Rpb248b2JqZWN0LCBvYmplY3Q+Pj4oKTsKCiAgICBwdWJsaWMgc3RhdGljIFQgU2V0PFQsIFRQcm9wZXJ0eT4oCiAgICAgICAgdGhpcyBUIGluc3RhbmNlLCAKICAgICAgICBFeHByZXNzaW9uPEZ1bmM8VCwgVFByb3BlcnR5Pj4gcHJvcEV4cHJlc3Npb24sCiAgICAgICAgVFByb3BlcnR5IG5ld1ZhbHVlKSB3aGVyZSBUIDogY2xhc3MKICAgIHsKICAgICAgICBHZXRTZXR0ZXJGb3IocHJvcEV4cHJlc3Npb24pKGluc3RhbmNlLCBuZXdWYWx1ZSk7CiAgICAgICAgcmV0dXJuIGluc3RhbmNlOwogICAgfQoKICAgIHByaXZhdGUgc3RhdGljIEFjdGlvbjxvYmplY3QsIG9iamVjdD4gR2V0U2V0dGVyRm9yPFQsIFRQcm9wZXJ0eT4oRXhwcmVzc2lvbjxGdW5jPFQsIFRQcm9wZXJ0eT4+IHByb3BFeHByZXNzaW9uKQogICAgewogICAgICAgIHZhciBtZW1iZXJFeHByZXNzaW9uID0gcHJvcEV4cHJlc3Npb24uQm9keSBhcyBNZW1iZXJFeHByZXNzaW9uOwogICAgICAgIGlmIChtZW1iZXJFeHByZXNzaW9uID09IG51bGwgfHwgbWVtYmVyRXhwcmVzc2lvbi5NZW1iZXIuTWVtYmVyVHlwZSAhPSBNZW1iZXJUeXBlcy5Qcm9wZXJ0eSkKICAgICAgICAgICAgdGhyb3cgbmV3IEludmFsaWRPcGVyYXRpb25FeGNlcHRpb24oIk9ubHkgcHJvcGVydHkgZXhwcmVzc2lvbnMgYXJlIHN1cHBvcnRlZCIpOwogICAgICAgIEFjdGlvbjxvYmplY3QsIG9iamVjdD4gc2V0dGVyID0gbnVsbDsKICAgICAgICBHZXRQcm9wTWFwPFQ+KCkuVHJ5R2V0VmFsdWUobWVtYmVyRXhwcmVzc2lvbi5NZW1iZXIuTmFtZSwgb3V0IHNldHRlcik7CiAgICAgICAgaWYgKHNldHRlciA9PSBudWxsKQogICAgICAgICAgICB0aHJvdyBuZXcgSW52YWxpZE9wZXJhdGlvbkV4Y2VwdGlvbigiTm8gc2V0dGVyIGZvdW5kIik7CiAgICAgICAgcmV0dXJuIHNldHRlcjsKICAgIH0KCiAgICBwcml2YXRlIHN0YXRpYyBJRGljdGlvbmFyeTxzdHJpbmcsIEFjdGlvbjxvYmplY3QsIG9iamVjdD4+IEdldFByb3BNYXA8VD4oKQogICAgewogICAgICAgIHJldHVybiBfbWFwLkdldE9yQWRkKHR5cGVvZihUKSwgeCA9PiBCdWlsZFByb3BNYXA8VD4oKSk7CiAgICB9CgogICAgcHJpdmF0ZSBzdGF0aWMgSURpY3Rpb25hcnk8c3RyaW5nLCBBY3Rpb248b2JqZWN0LCBvYmplY3Q+PiBCdWlsZFByb3BNYXA8VD4oKQogICAgewogICAgICAgIHZhciB0eXBlTWFwID0gbmV3IERpY3Rpb25hcnk8c3RyaW5nLCBBY3Rpb248b2JqZWN0LCBvYmplY3Q+PigpOwogICAgICAgIHZhciBmaWVsZHMgPSB0eXBlb2YoVCkuR2V0RmllbGRzKEZpZWxkRmxhZ3MpOwogICAgICAgIGZvcmVhY2ggKHZhciBwaSBpbiB0eXBlb2YoVCkuR2V0UHJvcGVydGllcyhQcm9wRmxhZ3MpKSAKICAgICAgICB7CiAgICAgICAgICAgIHZhciBiYWNraW5nRmllbGROYW1lcyA9IEJhY2tpbmdGaWVsZEZvcm1hdHMuU2VsZWN0KHggPT4gc3RyaW5nLkZvcm1hdCh4LCBwaS5OYW1lKSkuVG9MaXN0KCk7CiAgICAgICAgICAgIHZhciBmaSA9IGZpZWxkcy5GaXJzdE9yRGVmYXVsdChmID0+IGJhY2tpbmdGaWVsZE5hbWVzLkNvbnRhaW5zKGYuTmFtZSkgJiYgZi5GaWVsZFR5cGUgPT0gcGkuUHJvcGVydHlUeXBlKTsKICAgICAgICAgICAgaWYgKGZpID09IG51bGwpCiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgTm90U3VwcG9ydGVkRXhjZXB0aW9uKHN0cmluZy5Gb3JtYXQoIk5vIGJhY2tpbmcgZmllbGQgZm91bmQgZm9yIHByb3BlcnR5IHswfS4iLCBwaS5OYW1lKSk7CiAgICAgICAgICAgIHR5cGVNYXAuQWRkKHBpLk5hbWUsIChpbnN0LCB2YWwpID0+IGZpLlNldFZhbHVlKGluc3QsIHZhbCkpOwogICAgICAgIH0KICAgICAgICByZXR1cm4gdHlwZU1hcDsKICAgIH0KfQoKcHVibGljIGNsYXNzIFByb2dyYW0KewoJcHVibGljIHN0YXRpYyB2b2lkIE1haW4oKQoJewogICAgICAgIHZhciBteUFub25JbnN0YW5jZSA9IG5ldyB7IAogICAgICAgICAgICBGaXJzdEZpZWxkID0gIkhlbGxvIiwgCiAgICAgICAgICAgIEFub3RoZXJGaWVsZCA9IDMwLCAKICAgICAgICB9OwogICAgICAgIENvbnNvbGUuV3JpdGVMaW5lKG15QW5vbkluc3RhbmNlKTsKCiAgICAgICAgbXlBbm9uSW5zdGFuY2UKICAgICAgICAgICAgLlNldCh4ID0+IHguRmlyc3RGaWVsZCwgIkhlbGxvIFNPIikKICAgICAgICAgICAgLlNldCh4ID0+IHguQW5vdGhlckZpZWxkLCA0Mik7CiAgICAgICAgQ29uc29sZS5Xcml0ZUxpbmUobXlBbm9uSW5zdGFuY2UpOwoJfQp9