Created
May 31, 2019 20:33
-
-
Save thenameless314159/7102b2604c67610e728dee935109cdd4 to your computer and use it in GitHub Desktop.
Implementation of With from F# in C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Provide a thread-safe way to handle immutable objects. | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
public static class With<T> | |
{ | |
static With() => Cache.Register(); | |
/// <summary> | |
/// Create a new instance of <see cref="T"/> with the specified property to replace with the provided <see cref="value"/>. | |
/// </summary> | |
/// <typeparam name="TProp">The type of the property to modify.</typeparam> | |
/// <param name="instance">The instance of the class to modify.</param> | |
/// <param name="selector">The the property to modify.</param> | |
/// <param name="value">The value of the property to replace.</param> | |
/// <returns>A new instance of <see cref="T"/> with the specified property to replace with the provided <see cref="value"/>.</returns> | |
/// <exception cref="KeyNotFoundException"></exception> | |
/// <exception cref="ArgumentNullException"></exception> | |
/// <exception cref="NullReferenceException"></exception> | |
public static T Modify<TProp>(T instance, Expression<Func<T, TProp>> selector, TProp value) | |
{ | |
if (instance == null) throw new ArgumentNullException(nameof(instance)); | |
var member = selector.Body as MemberExpression; | |
var propInfo = member?.Member as PropertyInfo; | |
var clone = Cache.GetMethod(propInfo?.Name); | |
return clone(instance, value); | |
} | |
private static class Cache | |
{ | |
private static readonly Dictionary<string, Func<T, object, T>> _cloneMethods; | |
public static Func<T, object, T> GetMethod(string propertyName) => _cloneMethods[propertyName]; | |
public static void Register() { } | |
static Cache() | |
{ | |
var properties = typeof(T).GetProperties().Where(p => p.CanRead); | |
var methods = new Dictionary<string, Func<T, object, T>>(properties.Count()); | |
foreach (var property in properties) | |
methods.Add(property.Name, | |
CreateCloneFunc(property, properties).Compile()); | |
_cloneMethods = methods; | |
} | |
private static Expression<Func<T, object, T>> CreateCloneFunc(PropertyInfo fromProperty, IEnumerable<PropertyInfo> withProperties) | |
{ | |
var typeOfT = typeof(T); // reflection infos | |
var ctor = typeOfT.GetConstructors().First(c => c.GetParameters().Length == withProperties.Count()); | |
var propCount = withProperties.Count(); | |
var instance = Expression.Parameter(typeof(T), "instance"); // expr parameters | |
var value = Expression.Parameter(typeof(object), "value"); | |
var ctorArgs = new List<Expression>(propCount); | |
foreach (var prop in withProperties) | |
{ | |
if (prop == fromProperty) | |
ctorArgs.Add( | |
Expression.Convert(value, prop.PropertyType)); | |
else | |
ctorArgs.Add(Expression.Property(instance, prop)); | |
} | |
var newInstance = Expression.New(ctor, ctorArgs); | |
var lambda = Expression.Lambda<Func<T, object, T>>(newInstance, instance, value); | |
return lambda; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment