重构两种方法以使用接口和/或泛型

时间:2014-07-04 10:41:07

标签: c# generics interface refactoring

我有一个程序,它为多种类型的类传递一些查询条件,EF使用这些查询条件从数据库中查询数据。每个被查询的类型都有一个标准类,但是这些方法之间存在很多重复和相似之处,我希望重构这些方法,但没有成功。

这些是示例(简化)服务方法:

private static IQueryable<Class1> FilterClass1ResultsOnId(Class1QueryCriteria queryCriteria, IQueryable<Class1> queryResults)
    {
        if (!string.IsNullOrEmpty(queryCriteria.Id))
        {
            queryResults = from view in queryResults where view.Id==queryCriteria.Id select view;
        }
        return queryResults;
    }


private static IQueryable<Class2> FilterClass2ResultsOnId(Class2QueryCriteria queryCriteria, IQueryable<Class2> queryResults)
    {
        if (!string.IsNullOrEmpty(queryCriteria.Id))
        {
            queryResults = from view in queryResults where view.Id == queryCriteria.Id select view;
        }
        return queryResults;
    }

......就像这样被称为......

queryResults = FilterClass1ResultsOnId(queryCriteria, queryResults);

该服务有一系列这些'FilterClassXonSOMECRITERIA'方法来缩小最终返回的IQueryable。除了输入参数的类型和输出的类型之外,还有很多这些服务方法基本相同。

我最初尝试重构接受queryResults参数的方法,该参数是一个接口而不是一个具体的类 - 一个只有Id属性的简单接口 - 然后创建{{1} }和Class1实现该接口。但是,如果我只想使用一种方法,那么我还需要使这些方法的返回类型非具体。这就是我被卡住的地方。

我一直在环顾四周,看过协方差逆向信息,但我似乎无法定义使用这些接口和泛型类型的方法/接口没有得到'无法解析符号T'或'参数类型XXX不能分配给参数类型YYY''。

此时我想与那些更熟悉这个问题的人进行健全检查:我想做的是什么?我是否可以使用仅由接口指定参数的方法,并从该方法返回一个也仅由接口指定的对象?

编辑:标准类非常简单,实际上是EF模型中定义的复杂类型。但这是一个简化的例子:

Class2

2 个答案:

答案 0 :(得分:2)

您可以手动构建过滤器表达式:

private static Expression<Func<T, bool>> WhereById<T>(string id)
{
    var pExpr = Expression.Parameter(typeof(T));
    var idExpr = Expression.Property(pExpr, "Id");
    var eqExpr = Expression.Equal(idExpr, Expression.Constant(id));
    return Expression.Lambda<Func<T, bool>>(eqExpr, pExpr);
}

private static IQueryable<T> FilterById<T>(string id, IQueryable<T> source)
{
    if (!string.IsNullOrEmpty(queryCriteria.Id))
    {
        var whereExpr = WhereById<T>(string id);
        return source.Where(whereExpr);
    }
    return queryResults;
}

private static IQueryable<Class1> FilterClass1ResultsOnId(Class1QueryCriteria queryCriteria, IQueryable<Class1> queryResults)
{
    return FilterById(queryCriteria.Id, queryResults);
}

如果使用字符串Id属性创建基本类型,则可以避免需要单独的重载。

或者,您可以使条件类通用,并直接包含查询表达式,例如

public class QueryCriteria<T>
{
    public Expression<Func<T, bool>> FilterExpression { get; set; }
}
然后你可以写下这样的东西:

public static IQueryable<T> FilterResults<T>(QueryCriteria<T> criteria, IQueryable<T> source)
{
    return criteria.FilterExpression == null ? source : source.Where(criteria.FilterExpression);
}

答案 1 :(得分:1)

您应该能够为条件定义接口:

interface IQueryCriteria {
    string Id { get; set; }
}

..如有必要,还可以使用“视图”类型的界面:

interface IViewType {
    string Id { get; set; }
}

然后你的方法(非复数)可以是通用的:

private static IQueryable<T> FilterClassResultsOnId<T>(IQueryCriteria queryCriteria, IQueryable<T> queryResults) 
    where T : IViewType
{
    if (!string.IsNullOrEmpty(queryCriteria.Id))
    {
        queryResults = from view in queryResults where view.Id == queryCriteria.Id select view;
    }
    return queryResults;
}

这里的关键是泛型类型约束where T : IViewType。这允许在方法体中使用view.Id,因为编译器可以确认该类型至少会实现具有Id属性的接口。