Extract details from an Expression

I wanted to come up with a way of establishing the type name and property name when applying general validations within my code. So, for example for this validation:.

var bool = Requires.IsNotNull(user.lastName)

I have to explicitly code the exception:

throw NullReferenceException("User.lastName")

What I’d like is for all this to happen when I declare the test, without double typing the type and property name.

This is still very much a work in progress but I’ve got the basics of what I’m trying to achieve by using the Expression class.

Here’s how it’s used:

[Test]
public void FirstTest()
{
    var user = new User()
    {
        Name = "John",
        Age = 32,
        Skills = new List<string> { "digging", "mowing", "pruning" }
    };

    var decorator = new NaiveExpressionDecorator<User>(x => x.Name != null);

    decorator.propertyName.Should().Be("User.Name");
}

And here’s the basic implementation:

public sealed class NaiveExpressionDecorator<T>
{
    private readonly Expression<Func<T, bool>> rule;
    private readonly Func<T, bool> compiled;

    public NaiveExpressionDecorator(Expression<Func<T, bool>> rule)
    {
        this.rule = rule;
        compiled = this.rule.Compile();
    }

    public string propertyName
    {
        get
        {
            return PropertyName(this.rule.Body as dynamic);
        }
    }

    public bool Execute(T instance)
    {
        return compiled(instance);
    }

    private string PropertyName(BinaryExpression expression)
    {
        return PropertyName(expression.Left as dynamic);
    }

    private string PropertyName(MethodCallExpression expression)
    {
        if (expression.Object == null)
        {
            return PropertyName(expression.Arguments[0] as dynamic);
        }
        return PropertyName(expression.Object as dynamic);
    }

    private string PropertyName(MemberExpression expression)
    {
        string typeName = expression.Expression.Type.Name;
        return typeName + "." + expression.Member.Name;
    }
}

This is working for these tests:

private List<Tuple<Expression<Func<User, bool>>, string>> TestCases()
{
    return new List<Tuple<Expression<Func<User, bool>>, string>> {
        new Tuple<Expression<Func<User, bool>>, string>(
            x => x.Skills.Count() > 0, "User.Skills"),
        new Tuple<Expression<Func<User, bool>>, string>(
            x => x.Skills.Where(s => s.Length > 0).Contains("pruning"), "User.Skills"),
        new Tuple<Expression<Func<User, bool>>, string>(
            x => x.Age > 20, "User.Age"),
        new Tuple<Expression<Func<User, bool>>, string>(
            x => x.Name != null, "User.Name"),
        new Tuple<Expression<Func<User, bool>>, string>(
            x => x.Name == "John", "User.Name"),
        new Tuple<Expression<Func<User, bool>>, string>(
            x => x.Skills.Contains("mowing"), "User.Skills")};
}