fork(1) download
  1. using System;
  2. using System.Linq;
  3. using System.Reflection;
  4. using System.Linq.Expressions;
  5. using System.Collections.Generic;
  6. using System.Collections.Concurrent;
  7.  
  8. public static class AnonymousObjectMutator
  9. {
  10. private const BindingFlags FieldFlags = BindingFlags.NonPublic | BindingFlags.Instance;
  11. private const BindingFlags PropFlags = BindingFlags.Public | BindingFlags.Instance;
  12. private static readonly string[] BackingFieldFormats = { "<{0}>i__Field", "<{0}>" };
  13. private static ConcurrentDictionary<Type, IDictionary<string, Action<object, object>>> _map =
  14. new ConcurrentDictionary<Type, IDictionary<string, Action<object, object>>>();
  15.  
  16. public static T Set<T, TProperty>(
  17. this T instance,
  18. Expression<Func<T, TProperty>> propExpression,
  19. TProperty newValue) where T : class
  20. {
  21. GetSetterFor(propExpression)(instance, newValue);
  22. return instance;
  23. }
  24.  
  25. private static Action<object, object> GetSetterFor<T, TProperty>(Expression<Func<T, TProperty>> propExpression)
  26. {
  27. var memberExpression = propExpression.Body as MemberExpression;
  28. if (memberExpression == null || memberExpression.Member.MemberType != MemberTypes.Property)
  29. throw new InvalidOperationException("Only property expressions are supported");
  30. Action<object, object> setter = null;
  31. GetPropMap<T>().TryGetValue(memberExpression.Member.Name, out setter);
  32. if (setter == null)
  33. throw new InvalidOperationException("No setter found");
  34. return setter;
  35. }
  36.  
  37. private static IDictionary<string, Action<object, object>> GetPropMap<T>()
  38. {
  39. return _map.GetOrAdd(typeof(T), x => BuildPropMap<T>());
  40. }
  41.  
  42. private static IDictionary<string, Action<object, object>> BuildPropMap<T>()
  43. {
  44. var typeMap = new Dictionary<string, Action<object, object>>();
  45. var fields = typeof(T).GetFields(FieldFlags);
  46. foreach (var pi in typeof(T).GetProperties(PropFlags))
  47. {
  48. var backingFieldNames = BackingFieldFormats.Select(x => string.Format(x, pi.Name)).ToList();
  49. var fi = fields.FirstOrDefault(f => backingFieldNames.Contains(f.Name) && f.FieldType == pi.PropertyType);
  50. if (fi == null)
  51. throw new NotSupportedException(string.Format("No backing field found for property {0}.", pi.Name));
  52. typeMap.Add(pi.Name, (inst, val) => fi.SetValue(inst, val));
  53. }
  54. return typeMap;
  55. }
  56. }
  57.  
  58. public class Program
  59. {
  60. public static void Main()
  61. {
  62. var myAnonInstance = new {
  63. FirstField = "Hello",
  64. AnotherField = 30,
  65. };
  66. Console.WriteLine(myAnonInstance);
  67.  
  68. myAnonInstance
  69. .Set(x => x.FirstField, "Hello SO")
  70. .Set(x => x.AnotherField, 42);
  71. Console.WriteLine(myAnonInstance);
  72. }
  73. }
Success #stdin #stdout 0.08s 24800KB
stdin
Standard input is empty
stdout
{ FirstField = Hello, AnotherField = 30 }
{ FirstField = Hello SO, AnotherField = 42 }