如何从Lambda中检索属性的类型并在列表中推断它?

时间:2014-05-28 23:49:30

标签: c# generics reflection lambda sieve.net

背景

我试图开始a little Github open-source project.。它有助于创建可以Expression<Func<ABusinessObject, bool>>返回或编译为Func<ABusinessObject, bool>的过滤器。

目前,我必须以下列方式定义我的对象,包括, int>以指定属性类型:

new EqualitySieve<ABusinessObject, int>()
    .ForProperty(x=>x.AnInt)
    .ForValues("1, 2, 3");

它包含一个名为AcceptableValues的属性,它与我的代码中其他位置的属性TPropertyType的类型相同。)。我使用它来让用户看到Sieve当前可以接受哪些值。

目标

我希望能够删除, int>部分,并按以下方式编写此代码:

new EqualitySieve<ABusinessObject>()
    .ForProperty(x=>x.AnInt)
    .ForValues("1, 2, 3");

AcceptableValues列表的类型为int推断。

当前状态

允许我执行此操作的代码如下,几乎完全基于this SO question

/// <param name="propertyLambda">A lambda that indicates the property that we'd like to filter on.</param>
/// <remarks>
/// This is almost entirely possible due to the excellent answer on:
/// https://stackoverflow.com/questions/671968/retrieving-property-name-from-lambda-expression
/// </remarks>
public EqualitySieve<TTypeOfObjectToFilter, TPropertyType> ForProperty(Expression<Func<TTypeOfObjectToFilter, TPropertyType>> propertyLambda)
{
    Type typePropertyShouldBeFrom = typeof(TTypeOfObjectToFilter);
    var member = propertyLambda.Body as MemberExpression;
    if (member == null)
        throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.",propertyLambda));

    var propInfo = member.Member as PropertyInfo;
    if (propInfo == null)
        throw new ArgumentException(string.Format("Expression '{0}' refers to a field, not a property.",propertyLambda));

    Debug.Assert(propInfo.ReflectedType != null, "propInfo.ReflectedType != null");
    if (typePropertyShouldBeFrom != propInfo.ReflectedType &&
    !typePropertyShouldBeFrom.IsSubclassOf(propInfo.ReflectedType))
        throw new ArgumentException(string.Format("Expresion '{0}' refers to a property that is not from type {1}.",propertyLambda,
                typePropertyShouldBeFrom));

    PropertyToFilter = propInfo;

    return this;
}

问题

有一种优雅的方式来进行转换吗?我并不担心API,因为该项目处于pre-alpha状态,但如果我能保留AcceptableValues列表并确保它与属性保持相同类型,那就太棒了。

注意:从技术上讲,任何Sieve都需要在属性上运行,我也可以删除ForProperty并将lambda表达式放在构造函数中,如果这样做的话帮助迈向更优雅的解决方案。

PS。在我想要学习的同时,也可以随意提交拉取请求(这是issue #27),我很乐意为您提供项目贡献者的信任。

1 个答案:

答案 0 :(得分:1)

即使我无法提供任何结论,也许我可以帮助指出正确的方向。

重点在于您需要使用泛型方法,这些方法可以推断类型(泛型类不能)。也就是说,如果您有这样的方法:

public class Foo
{
    public void Something<T>(T value) { }
}

然后你可以简单地编写new Foo().Something("a string"),编译器将推断Tstring。但如果泛型是在类级别:

public class Foo<T>
{
    public void Something(T value) { }
}

然后,您必须明确指定T,如new Foo<string>().Something("a string")

应用上述内容,我会说你需要的是一个&#34; builder&#34;构造中间值的类。鉴于你想要的语法,我建议如下:

// sieve will be of type "EqualitySieve<ABusinessObject, int>"

var sieve = new EqualitySieveBuilder<ABusinessObject>()
    .ForProperty(x=>x.AnInt)
    .ForValues("1, 2, 3");

EqualitySieveBuilder<T>类是一种中间类,其函数返回实际的EqualitySieve<T, TProperty>个实例:

public class EqualitySieveBuilder<T>
{
    public EqualitySieve<T, TProperty> ForProperty<TProperty>(
        Expression<Func<T, TProperty>> propertyExpression)
    {
        return new EqualitySieve<T, TProperty>()
            .ForProperty(propertyExpression);
    }
}

然后,您可以通过这种方法获得更多,添加多个接口,封装构建最终实例的各个中间阶段。