c# - 具有可选泛型参数的扩展方法

标签 c# linq generics extension-methods

我需要实现基于扩展方法的 API(即我必须使用静态非泛型类)。 API 应该与 LINQ fluent API 一起顺利工作,并且主要与 IQueryable 参数一起工作。像这样:

public static class SomeExtensions
{
    public static IQueryable<TEntity> SomeMethod<TEntity>(this IQueryable<TEntity> set, ... some arguments)
    {
    }
}

现在,假设该方法应该接受一些参数和一个 Expression<Func<TEntity, TResult>>。一:

    public static IQueryable<TEntity> SomeMethod<TEntity, TResult>(
        this IQueryable<TEntity> set,
        ...,
        Expression<Func<TEntity, TResult>> orderByExpression)
    {
    }

我想将 orderByExpression 传递给 Fluent API 的 OrderBy 方法。或者如果 orderByExpression == null 做其他事情.

当然,我想要这样的东西:

    public static IQueryable<TEntity> SomeMethod<TEntity, TResult>(
        this IQueryable<TEntity> set,
        ...,
        Expression<Func<TEntity, TResult>> orderByExpression = null)
    {
    }

...但是在不带可选参数的情况下调用此方法时,我必须隐式传递泛型类型,因为编译器不知道 TResult 的类型。

我看到了一些可能的方法,但我不太喜欢它们。

  1. 定义两个方法:一个有这个参数,一个没有,然后从第二个调用第一个。我不喜欢它,因为实际上 API 中有很多这样的方法,我必须为每个方法定义一个额外的方法。

  2. 使用 Expression<Func<TEntity, object>>而不是 Expression<Func<TEntity, TResult>> (目前是这样)。我摆脱了泛型类型,但是像 int 这样的简单(值)类型存在问题:LINQ 在尝试将 System.Int32 转换为 System.Object 时引发异常。

  3. 也许(还没有试过)我可以使用 Expression<Func<TEntity, dynamic>> - 但我认为这根本不是一个好方法。

还有其他想法吗?

最佳答案

从调用者的角度来看,选项 (1) 是最好的。请记住,API 的主要目标是让调用者的生活更轻松,因此在实现方面付出额外的努力应该是值得的。

选项(3)不好。您不想输入由 dynamic 类型引入的并发症。而且 EF 不喜欢动态表达式。

选项(2)其实还不错。因此,如果它是您当前使用的,您可以继续使用它。要使 EF 满意,您只需通过删除为值类型属性引入的 Convert 来转换传递的表达式。为此,您可以使用以下辅助方法:

internal static IQueryable<T> ApplyOrderBy<T>(
    this IQueryable<T> source,
    Expression<Func<T, object>> orderByExpression = null)
{
    if (orderByExpression == null) return source;
    var body = orderByExpression.Body;
    // Strip the Convert if any
    if (body.NodeType == ExpressionType.Convert)
        body = ((UnaryExpression)body).Operand;
    // Create new selector
    var keySelector = Expression.Lambda(body, orderByExpression.Parameters[0]);
    // Here we cannot use the typed Queryable.OrderBy method because
    // we don't know the TKey, so we compose a method call instead
    var queryExpression = Expression.Call(
        typeof(Queryable), "OrderBy", new[] { typeof(T), body.Type },
        source.Expression, Expression.Quote(keySelector));
    return source.Provider.CreateQuery<T>(queryExpression);
}

这是一个小测试,展示了上述方法如何适用于不同的属性类型:

var input = new[]
{
    new { Id = 2, Name = "B", ParentId = (int?)1 },
    new { Id = 1, Name = "A", ParentId = (int?)null },
}.AsQueryable();

var output1 = input.ApplyOrderBy(e => e.Id).ToList();
var output2 = input.ApplyOrderBy(e => e.Name).ToList();
var output3 = input.ApplyOrderBy(e => e.ParentId).ToList();

示例用法与您的示例:

public static IQueryable<TEntity> SomeMethod<TEntity>(
    this IQueryable<TEntity> source,
    ...,
    Expression<Func<TEntity, object>> orderByExpression = null)
{
    var result = source;
    result = preprocess(result);
    result = result.ApplyOrderBy(orderByExpression);
    result = postprocess(result);
    return result;    
}

关于c# - 具有可选泛型参数的扩展方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38976289/

相关文章:

c# - 无法找到程序集 Entity Framework v 4.4.0.0

c# 将 xml 加载到组合框中

java - Java扩展ArrayList时如何添加map和filter?

c# - 通用参数委托(delegate)?

c# - 比较 C# 中的盒装对象

c# - Windows Mobile 项目中的 System.Reflection

c# - 如何在 ASPxGridView 的一个单元格中聚合一组项目

C#-选择另一个字典中不存在的键和值

class - 从协议(protocol)调用类方法作为参数

go - Golang 中的通用结构/接口(interface)列表