2

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?

Simon
  • 4,157
  • 2
  • 46
  • 87
  • 2
    Why is this so complex? The method for trimming the first or last name is exactly the same code, as a string is anyway an immutable object then you would be better to simply create a "method which is called such as "person.LastName = TrimName(person.LastName);". And then just call this on each property when required. – jason.kaisersmith Apr 06 '23 at 05:01
  • Because inside the method, the property-name of the trimmed property should be printed out to the console (logger) – Simon Apr 06 '23 at 05:07
  • For logging purposes? Then just called your logger before/after each call to the TrimName method for that specific property. You already have about twice as many lines of code than you actually need. – jason.kaisersmith Apr 06 '23 at 05:20
  • @jason.kaisersmith: The method TrimName() is only a simplified example of that what we have in real. In real, there is much more code that needs access to the Property. – Simon Apr 06 '23 at 05:43
  • 1
    Ah, you see, this is critical information that is missing! Even if you don't include the code, you need to tell us this information so that we know the whole picture and don't go down a dead-end! – jason.kaisersmith Apr 06 '23 at 05:48

2 Answers2

2

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);
Sweeper
  • 213,210
  • 22
  • 193
  • 313
0

You can use a dynamic type and use reflection to get property names:

public static void TrimName<T>(this Person person, Func<Person, T> action)
{
    var propertyNames = action(person).GetType().GetProperties().Select(x => x.Name);
    foreach(var propertyName in propertyNames) {
        var property = typeof(Person).GetProperty(propertyName);
        string currentValue = (string)property.GetValue(person);
        string trimmedValue = currentValue.Remove(32);
        property.SetValue(person, trimmedValue);
    }
}

Can be used like this:

var person = new Person { FirstName = "Foo", LastName = "Bar" };
person.TrimName(x => new { x.FirstName, x.LastName });

As you can see from the example, you can trim both properties in one call if you wish. :)

GregorMohorko
  • 2,739
  • 2
  • 22
  • 33