4

For mock/testing purposes, I'd like to have a method that I can pass in a collection of objects of some type, a Func to select the property to update, and the value I'd like to set each property to. I want to do this without using reflection. I could also make it an extension method and wrap it for mocking, but that's for after this is solved.

Here's an example to update a property Bar for each in a List<Foo>:

public class Foo 
{
    public string Bar { get; set; }
}

// I want something like this
public List<Foo> UpdateProp_Ideal<TProperty>(List<Foo> foos, Func<Foo, TProperty> propertyFunc, TProperty valueToSet)
{
    return foos.Select(x => { propertyFunc(x)[Somehow get setter?] = valueToSet; return x; }).ToList();
}

// I could do this, but it has a broader scope (and more typing)
public List<Foo> UpdateProp(List<Foo> foos, Func<Foo, Foo> updateFunc)
{
    return foos.Select(updateFunc).ToList();
}

// Ideal call
var updatedFoos = UpdateProp_Ideal(listOfFoos, x => x.Bar, "Updated!");

// Working call
var updatedFoos = UpdateProp(listOfFoos, x => { x.Bar = "Updated!"; return x; });
Jecoms
  • 2,558
  • 4
  • 20
  • 31
  • Is there any particular reason that you do not want to use reflection? – Xiaosu Oct 12 '18 at 02:44
  • Use expressions (first approach) https://stackoverflow.com/a/2824409/1099716 – Access Denied Oct 12 '18 at 03:43
  • a `foreach` with an `action` delegate would do the same thing. Not understanding the need for the Func. there really is not need to return anything if just modifying the objects in the collection – Nkosi Oct 12 '18 at 03:57
  • @Xiaosu For me personally, I like to avoid using reflection when possible because it is not simple to follow for anyone stumbling upon it in the future. The codebase I work on certainly has some, but it's only used when there is no other option. – Jecoms Oct 12 '18 at 04:40
  • @AccessDenied I'll probably end up using something like that if it's not possible without refection. – Jecoms Oct 12 '18 at 04:42
  • @Nkosi It returns a new list because the call could be made across a service boundary and also in the interest of being more functional. – Jecoms Oct 12 '18 at 04:42
  • 2
    @Jecoms it does not use Reflection. It uses System.Linq.Expressions and that's it. – Access Denied Oct 12 '18 at 04:48
  • @AccessDenied You're right. I'll try that out, thanks. – Jecoms Oct 12 '18 at 05:21

1 Answers1

4
public static List<T> UpdateProp_Ideal<T, TProperty>(
    this List<T> foos, Expression<Func<T, TProperty>> propertyFunc, TProperty valueToSet)
{
    var body = Expression.MakeBinary(
        ExpressionType.Assign, propertyFunc.Body, Expression.Constant(valueToSet)
    );
    var action = Expression.Lambda<Action<T>>(body, propertyFunc.Parameters).Compile();
    foos.ForEach(action);
    return foos;
}

Usage:

var updatedFoos = UpdateProp_Ideal(listOfFoos, x => x.Bar, "Updated!");
Slava Utesinov
  • 13,410
  • 2
  • 19
  • 26
  • As C# becomes more functional, I'd like to see something like this supported by new language features, but this works well enough for now. I only modified return to `return foos.Select(x => { action(x); return x; }).ToList();` – Jecoms Oct 12 '18 at 14:42