从lambda获取属性名称,该lambda将动态参数作为输入

时间:2016-06-30 12:40:35

标签: c# .net dynamic lambda

我正在设计一个测试API。我希望有一个像这样的API:

// 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);

我想写TestProperty方法来断言属性并显示正确的消息它失败的方式如下:

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);
}

在此示例中,我需要GetPropertyPathFromFunc的正文。

问题如何编写一个方法,将o => City.Name之类的lambda作为输入,并返回类似"City.Name"的字符串作为结果。

2 个答案:

答案 0 :(得分:2)

当您使用dynamic时,您会失去类型安全性并编译时间成员名称检查,因此将字符串用作属性名称没有任何区别。

这是一个解决方案。它需要广泛的错误检查和异常处理。您还可以添加缓存机制以减少反射开销。

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");

答案 1 :(得分:1)

如上所述,遗憾的是dynamic无法在C#编译器当前实现的表达式树中使用。作为替代方法,您可以使用自定义动态对象调用委托,该对象收集所访问的属性名称。我在下面演示了这一点。请注意,这仅适用于您给出的有限语法,并且我没有花费太多精力来处理更复杂的事情。

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;
    }
}