Context: we have the following code example:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
public static class PersonExtension
{
public static void TrimFirstName(Person person)
{
Console.WriteLine($"Start Trim {nameof(person.FirstName)}");
person.FirstName = person.FirstName.Remove(32);
Console.WriteLine($"End Trim {nameof(person.FirstName)}");
}
public static void TrimLastName(Person person)
{
Console.WriteLine($"Start Trim {nameof(person.LastName)}");
person.LastName = person.LastName.Remove(32);
Console.WriteLine($"End Trim {nameof(person.LastName)}");
}
}
public class Program
{
public static void Main()
{
var person = new Person { FirstName = "Foo", LastName = "Bar" };
PersonExtension.TrimFirstName(person);
PersonExtension.TrimLastName(person);
}
}
Target: we want to refactor the code and transform the functions TrimFirstName
and TrimLastName
into a single function to make it more dry.
We are currently stuck at a lambda expression to try to solve the problem:
public static class PersonExtension
{
public static void TrimName(this Person person, Func<Person, string> action)
{
// Console.WriteLine($"Start Trim {nameof(person.LastName)}"); // ??
person.LastName = action(person).Remove(32); // how to assign to LastName/FirstName ???
// Console.WriteLine($"End Trim {nameof(person.LastName)}"); // ??
// more code here that uses person.<propertyName>
}
}
public class Program
{
public static void Main()
{
var person = new Person { FirstName = "Foo", LastName = "Bar" };
person.TrimName(x => x.FirstName);
}
}
Question: how can we use a lambda expression to decide which property should be trimmed?
If you just want to get the property's value and also get its name, you can take an expression tree (similar to my answer here):
public static void TrimName(this Person person, Expression<Func<Person, string>> propertySelector)
{
if (propertySelector.Body is MemberExpression memberAccess) {
var getter = propertySelector.Compile();
var propertyValue = getter(person);
var propertyName = memberAccess.Member.Name;
} else {
// lambda is not a member access
}
}
If you also want to set the property however, I can't think of anything better than using reflection to call PropertyInfo.SetValue
:
public static void TrimName(this Person person, Expression<Func<Person, string>> propertySelector)
{
if (propertySelector.Body is MemberExpression memberAccess &&
memberAccess.Member is PropertyInfo propertyInfo &&
propertyInfo.CanWrite) {
var getter = propertySelector.Compile();
Console.WriteLine($"Start Trim {memberAccess.Member.Name}");
propertyInfo.SetValue(person, getter(person).Remove(32));
Console.WriteLine($"End Trim {memberAccess.Member.Name}");
} else {
// lambda is not a settable property
}
}
The other option is to pass in another lambda that is dedicated to setting the property, turning the usage into:
person.TrimName(x => x.LastName, (x, name) => x.LastName = name);