mehrandvd
mehrandvd

Reputation: 9116

Get property name from a lambda which takes a dynamic parameter as input

I'm designing a test API. I want to have an API like:

// There is a dynamic object which should be tested to have certain properties.
dynamic result = SomeMethod();

AssertPropertyIsNotNull(resut, o => o.Title);
AssertPropertyIsNotNull(resut, o => o.City.Name);

I want to write TestProperty method to assert the property and shows a proper message it it fails like:

private void AssertPropertyIsNotNull(dynamic result, Func<dynamic, object> propertySelector)
{
    var propertyPath = GetPropertyPathFromFunc(propertySelector);
    var errorMessage = $"{propertyPath} is not filled properly."
    Assert.IsNotNull(propertySelector(result), errorMessage);
}

Here in this example, I need the body for GetPropertyPathFromFunc.

Question How can I write a method that gets a lambda like o => City.Name as input and returns a string like "City.Name" as result.

Upvotes: 1

Views: 755

Answers (2)

Hamid Pourjam
Hamid Pourjam

Reputation: 20754

As you are using dynamic you loose type safety and compile time member name checking checking so it does not make any difference to use strings as property names.

Here is a solution. It needs extensive error checking and exception handling. You can also add caching mechanism to reduce reflection overhead.

public static bool IsPropertyNull(dynamic obj, string propertyName)
{
    var path = propertyName.Split('.');
    object tempObject = obj;
    for (int i = 0; i < path.Length; i++)
    {
        PropertyInfo[] dynamicProperties = tempObject.GetType().GetProperties();
        var property = dynamicProperties.Single(x => x.Name == path[i]);
        tempObject = property.GetValue(tempObject);
    }
    return tempObject == null;
}

bool isTitleNull = IsPropertyNull(result, "Title");
bool isCityNameNull = IsPropertyNull(result, "City.Name");

Upvotes: 2

Dark Falcon
Dark Falcon

Reputation: 44181

As noted, unfortunately dynamic can't be used in expression trees as currently implemented by the C# compiler. As an alternative, you could invoke the delegate with a custom dynamic object which collects the property names accessed. I have demonstrated this below. Note that this only works with the limited syntax you have given, and I have not made a lot of effort to handle anything more complex.

private static string GetPropertyPathFromFunc(Func<dynamic, object> propertySelector)
{
    var collector = new PropertyNameCollector();
    propertySelector(collector);
    return collector.Name;
}

private class PropertyNameCollector : DynamicObject
{
    public string Name { get; private set; }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (!string.IsNullOrEmpty(Name))
            Name += ".";
        Name += binder.Name;
        result = this;
        return true;
    }
}

Upvotes: 1

Related Questions