Lambda expressions are a powerful feature of the .NET stack, allowing developers to “express” an operation. Some of the most popular OSS libraries in .NET today take advantage of expressions: FluentValidation, AutoMapper, Dapper, and Marten. Library authors use expressions to help interpret a user’s intent into executable code, and most commonly used in LINQ implementations. Expressions can be beneficial in code-as-configuration scenarios, where our types and values are .NET objects.

In this short yet valuable post, we’ll implement extension methods that allow us to retrieve the PropertyInfo of a lambda expression, helpful in building our expression-powered APIs.

Using Expressions For Mapping

First, let’s look at an example that might look familiar to users of AutoMapper.

[Fact]
public void Map()
{
    var mapper =
        new Mapper<LambdaTests.First, LambdaTests.Second>();

    mapper.Map(f => f.Id, t => t.Name);
}

Where are taking a source and mapping it to a target destination. The API of our Mapper type might look something like this. I’ve left out internal implementation for brevity.

public class Mapper<TSource,TTarget>
{
    public Mapper<TSource, TTarget> Map<TProperty>(
        Expression<Func<TSource, TProperty>> from,
        Expression<Func<TTarget, TProperty>> to
    )
    {
        // store mappings
        return this;
    }

    public TTarget To(TSource source)
    {
        // map from source to target
        return default;
    }

    public TSource From(TTarget target)
    {
        // reverse map from target to source
        return default;
    }
}

From here, we can access the type of our targets and destinations, and with the additional member expressions, we can also retrieve the PropertyInfo defined by the developer.

While this approach is contrived, we could use the mapping information for processing values as we map into our target destination from the source. A good example might be transforming sensitive data like credit card information, government identifiers, and date of birth.

Now that we have expressions, how do we retrieve the PropertyInfo of an expression x => x.Id?

Retrieving PropertyInfo From A Lambda Expression

We can use the System.Linq.Expressions namespace to work through the user-defined expression.

using System;
using System.Linq.Expressions;
using System.Reflection;

public static class Lambdas
{
    public static PropertyInfo GetPropertyInfo<TType, TReturn>(
        this Expression<Func<TType, TReturn>> property
    )
    {
        LambdaExpression lambda = property;
        var memberExpression = lambda.Body is UnaryExpression expression
            ? (MemberExpression) expression.Operand
            : (MemberExpression) lambda.Body;

        return (PropertyInfo) memberExpression.Member;
    }

    public static string GetPropertyName<TType, TReturn>(
        this Expression<Func<TType, TReturn>> property
    ) => property.GetPropertyInfo().Name;
}

Example usages from a unit testing class show how to use the extension methods.

using System;
using System.Linq.Expressions;
using Xunit;

public class LambdaTests
{
    [Fact]
    public void Can_get_name_of_Id()
    {
        Expression<Func<First,string>> expression = 
            f => f.Id;
        
        Assert.Equal(
            nameof(First.Id), 
            expression.GetPropertyName());
    }

    [Fact]
    public void Can_get_propertyInfo_of_Name()
    {
        Expression<Func<Second,string>> expression =
            f => f.Name;
        
        Assert.Equal(
            nameof(Second.Name),
            expression.GetPropertyInfo().Name);
    }

    [Fact]
    public void Can_set_value_using_property_info()
    {
        Expression<Func<Third,Guid>> expression =
            f => f.Guid;
        
        var info = expression.GetPropertyInfo();
        var expected = Guid.NewGuid();

        var target = new Third(Guid.Empty);
        info.SetValue(target, expected);
        
        Assert.Equal(expected, target.Guid);
    }

    public record First(string Id);
    public record Second(string Name);
    public record Third(Guid Guid);
    
}

We have the added benefit of having access to PropertyInfo, which allows us to set the value of any property at runtime. We can use the method SetValue and an instance of our target type. Pretty cool!

Conclusion

Expressions are a powerful feature of the .NET stack as they can help users express intent ahead of time. In OSS libraries like FluentValidation and Automapper, developers use expressions as in-code configuration. If you’re planning on building a similar API, be prepared for many generics, expressions, and reflections. A warning to developers, reflection can come at a performance expense. Developers should measure their implementation and its impacts during real-world scenarios.

I hope you found this post helpful, and thanks again for reading.