Skip to content

Instantly share code, notes, and snippets.

@thenameless314159
Created May 31, 2019 20:33
Show Gist options
  • Save thenameless314159/7102b2604c67610e728dee935109cdd4 to your computer and use it in GitHub Desktop.
Save thenameless314159/7102b2604c67610e728dee935109cdd4 to your computer and use it in GitHub Desktop.
Implementation of With from F# in C#
/// <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