Any ideas how to create a generic fluent setter?
Imagine that I've the following class
internal class ClonableExampleClass
{
public ClonableExampleClass()
{
}
public string ExampleString { get; set; }
public int ExampleInt { get; set; }
public ClonableExampleClass ExampleNestedClass { get; set; }
public List<ClonableExampleClass> ExampleList { get; set; }
}
I want to:
public class Program
{
public static Task Main(string[] args)
{
var exampleClass = new ClonableExampleClass
{
ExampleInt = 1
};
exampleClass
.With(opt => opt.ExampleInt).Set(2)
.With(opt => opt.ExampleString).Set("test");
var json = JsonSerializer.Serialize(exampleClass);
}
}
and the expected json to be:
{"ExampleString":"test","ExampleInt":2,"ExampleNestedClass":null,"ExampleList":null}
I've created an extension method With:
public static FluentBuilderObjectValueSetter<T> With<T>(this T sourceObject, Func<T,object> action)
{
var property = action(sourceObject);
return new FluentBuilderObjectValueSetter<T>(sourceObject, action);
}
Which returns FluentBuilderObjectValueSetter (I've removed the interface to make it more easy to test, for now)
public class FluentBuilderObjectValueSetter<TObjectType> //:IFluentBuilderObjectValueSetter<TObjectType>
{
private readonly TObjectType _sourceObject;
private object _sourceValue;
internal FluentBuilderObjectValueSetter(TObjectType sourceObject, ref object sourceValue)
{
_sourceObject = sourceObject;
//_action = action;
_sourceValue = sourceValue;
}
public TObjectType Set<TMemberType>(TMemberType value)
{
_sourceValue = value;
return _sourceObject;
}
}
But is not setting the object value
CodePudding user response:
If you need to write the code like this way, you can manipulate your source object with the with and set method flow as you want with the code block I wrote below.
Class below, hold source object and member selector expression:
public class FluentBuilder<T, TMember>
{
private readonly T _source;
private readonly Expression<Func<T, TMember>> _field;
public FluentBuilder(T source, Expression<Func<T, TMember>> field)
{
_source = source;
_field = field;
}
public T Set(TMember value)
{
MemberInfo memberInfo = GetMemberInfo(_field);
SetMemberValue(memberInfo, _source, value);
return _source;
}
private static MemberInfo GetMemberInfo(Expression<Func<T, TMember>> expression)
{
if (expression.Body is MemberExpression member)
return member.Member;
throw new ArgumentException("Expression is not a member access", nameof(expression));
}
private static void SetMemberValue(MemberInfo member, T target, object value)
{
switch (member.MemberType)
{
case MemberTypes.Field:
((FieldInfo)member).SetValue(target, value);
break;
case MemberTypes.Property:
((PropertyInfo)member).SetValue(target, value, null);
break;
default:
throw new ArgumentException("MemberInfo must be if type FieldInfo or PropertyInfo", nameof(member));
}
}
}
The code below create FluentBuilder With
extension method.
public static class FluentBuilderExtension
{
public static FluentBuilder<T, TMember> With<T, TMember>(this T obj, Expression<Func<T, TMember>> field)
{
return new FluentBuilder<T, TMember>(obj, field);
}
}
Using code blocks which are above, you can write the code like this:
public static void Main(string[] args)
{
var exampleClass = new ClonableExampleClass
{
ExampleInt = 1
};
exampleClass.With(x => x.ExampleInt).Set(2)
.With(x => x.ExampleString).Set("test");
string json = JsonSerializer.Serialize(exampleClass);
Console.WriteLine(json);
}
CodePudding user response:
Drawing some inspiration from this answer, I came up with the following:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
public class FluentBuilderObjectValueSetter<TObjectType, TPropertyType>
{
private readonly TObjectType _sourceObject;
private readonly Expression<Func<TObjectType, TPropertyType>> _selector;
internal FluentBuilderObjectValueSetter(TObjectType sourceObject, Expression<Func<TObjectType, TPropertyType>> selector)
{
_sourceObject = sourceObject;
_selector = selector;
}
internal TObjectType Set(TPropertyType value) {
var memberSelectorExpression = _selector.Body as MemberExpression;
if (memberSelectorExpression is not null)
{
var property = memberSelectorExpression.Member as PropertyInfo;
if (property is not null)
{
property.SetValue(_sourceObject, value, null);
}
}
return _sourceObject;
}
}
public static class ExtensionMethods {
public static FluentBuilderObjectValueSetter<TObjectType, TPropertyType> With<TObjectType, TPropertyType>(this TObjectType sourceObject, Expression<Func<TObjectType, TPropertyType>> selector)
{
return new FluentBuilderObjectValueSetter<TObjectType, TPropertyType>(sourceObject, selector);
}
}
public class Program
{
internal class ClonableExampleClass
{
public string ExampleString { get; set; }
public int ExampleInt { get; set; }
public ClonableExampleClass ExampleNestedClass { get; set; }
public List<ClonableExampleClass> ExampleList { get; set; }
}
public static void Main()
{
var exampleClass = new ClonableExampleClass
{
ExampleInt = 1
};
exampleClass
.With(opt => opt.ExampleInt).Set(2)
.With(opt => opt.ExampleString).Set("test");
var json = JsonSerializer.Serialize(exampleClass);
Console.WriteLine(json);
}
}