Home > other >  C# Fluent Generic Object Property Value Setter
C# Fluent Generic Object Property Value Setter

Time:01-12

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);
    }
}
  •  Tags:  
  • c#
  • Related