使用lambda表示法时,如何在C#中解释泛型TKey?

时间:2015-10-13 16:17:07

标签: c# linq generics polymorphism expression

我想在基类中使用一些System.Linq扩展方法,而派生类应该能够通过覆盖特定方法来提供一些表达式。

当前基类代码:

lQuery.OrderBy(s => s.ID) <-- works, *1

我希望通过可以覆盖的方法调用替换s => s.ID

... Expression<Func<TSource, TKey>> GetOrderByKey<TSource, TKey>()
{
  ...
} 

但是当我覆盖像

这样的方法时
override Expression<Func<TSource, TKey>> GetOrderByKey<TSource, TKey>()
{
    return s => s.ID; <-- compiler error, *2
}

编译器输出错误

  

无法将类型'int'隐式转换为'TKey'

  1. 为什么通用参数TKey在第1行中神奇地推导出(和什么?),但在第2行中,它被推断为ID属性的实际类型?

  2. 如何解决编译错误?

  3. 提前致谢。

    修改

    我很难找到问题的正确解释。我们将其分解为以下简单的行:

      var lQuery = from s in ... select s;
    
      lQuery = lQuery.OrderBy(s => s.ID); // *3
    

    编译器解释OrderBy调用究竟是如何产生ORDER BY ID ASC而不是ORDER BY %Value of ID% ASC?编译器似乎以某种方式决定推导s.IDproperty name "ID",而不是采用实际的属性数据类型,从而得到int值。

    EDIT2

    好的,+ D Stanley的另一个例子

    这有效:

    void SetOrder<TKey>(IQueryable<%type of s%> aQuery, Expression<Func<%type of s%, TKey>> aKeySelector)
    {
        aQuery.OrderBy(aKeySelector);
    }
    
    ...
    
    SetOrder(aQuery, s => s.ID); // <-- works
    

    但这不是(编译错误显示如前所述)

    protected void SetOrder<TKey>(IQueryable<%type of s%> aQuery)
    {
        Expression<Func<%type of s%, TKey>> lKeySelector = s => s.ID; // <-- deduced to int
    
        aQuery.OrderBy(lKeySelector);
    }
    

2 个答案:

答案 0 :(得分:1)

  

OrderBy如何推断TSourceTKey的类型

编译器可以通过查看:

来推断TSourceTKey的类型
  1. lQuery的类型 - 在本例中为IQueryable<{your class}>
  2. 表达式的返回类型,在本例中为ID
  3. {your class}属性的类型
      

    如何解决编译错误?

    您要求调用者指定TKey的类型(通过通用参数),但在您的方法中,您指定的是已知的表达式输入的。您可以删除泛型参数:

    protected void SetOrder(IQueryable<%type of s%> aQuery)
    {
      Expression<Func<%type of s%, int>> lKeySelector = s => s.ID; 
    
      aQuery.OrderBy(lKeySelector);
    }
    

    但请注意,OrderBy 会返回一个查询 - 它不会更改原始查询,所以真正想要的是类似的东西:

    protected IQueryable<%type of s%> SetOrder(IQueryable<%type of s%> aQuery)
    {
      Expression<Func<%type of s%, int>> lKeySelector = s => s.ID; 
    
      return aQuery.OrderBy(lKeySelector);
    }
    

    可能

    protected void SetOrder(ref IQueryable<%type of s%> aQuery)
    {
      Expression<Func<%type of s%, int>> lKeySelector = s => s.ID; 
    
      aQuery = aQuery.OrderBy(lKeySelector);
    }
    

    但通常不鼓励使用ref参数 - 最好返回一个新值,而不是更改原始值。

答案 1 :(得分:0)

我想我已经以一种没有人理解我的意图的方式写了我的问题。我的目标是避免为需要排序的每个字段一次又一次地编写相同的.OrderBy() / .ThenBy()代码(* 1)。

所以我想到了将 SortKey 添加到混音中,当 SortKey 匹配(* 2)时,我调用一个扩展方法来执行相应的调用排序(* 3)。像魅力一样。

IQueryable 扩展方法

public static System.Linq.IQueryable<TRecord> Sort<TRecord, TKey>(this System.Linq.IQueryable<TRecord> aQuery, 
  System.Linq.Expressions.Expression<System.Func<TRecord, TKey>> aSortFieldSelector, SortOrder aSortOrder, bool aIsPrimarySort)
{
  System.Linq.IOrderedQueryable<TRecord> lOrderedQuery = aIsPrimarySort ? null : aQuery as System.Linq.IOrderedQueryable<TRecord>;

  // *1
  if (aSortOrder == SortOrder.Descending)
  {
    if (lOrderedQuery == null)
      lOrderedQuery = aQuery.OrderByDescending(aSortFieldSelector);
    else
      lOrderedQuery = lOrderedQuery.ThenByDescending(aSortFieldSelector);
  }
  else
  {
    if (lOrderedQuery == null)
      lOrderedQuery = aQuery.OrderBy(aSortFieldSelector);
    else
      lOrderedQuery = lOrderedQuery.ThenBy(aSortFieldSelector);
  }

  if (lOrderedQuery != null)
    return lOrderedQuery.AsQueryable();
  else
    return aQuery;
}    

基础课程

public class BaseClass
{
  // public
    public void DoSomething()
    {
      System.Linq.IQueryable<TRecord> lQuery = ...
      System.Collections.Generic.Dictionary<string, SortOrder> lSorting = ...

      ...
      SortQuery(lQuery, lSorting);
      ...
    }

  // protected
    protected abstract void SortQuery(ref System.Linq.IQueryable<TRecord> aQuery, string aSortKey, SortOrder aSortOrder, bool aIsPrimarySort);

  // private
    private void SortQuery(ref System.Linq.IQueryable<TRecord> aQuery, System.Collections.Generic.Dictionary<string, SortOrder> aSorting)
    {
      bool lIsPrimarySort = true;

      foreach (string lSortKey in aSorting.Keys)
      {
        SortQuery(ref aQuery, lSortKey, aSorting[lSortKey], lIsPrimarySort);

        lIsPrimarySort = false;
      }
    }
}

派生类

public class DerivedClass : BaseClass
{
  // protected
    protected override void SortQuery(ref IQueryable<CouponTableRecord> aQuery, string aSortKey, SortOrder aSortOrder, bool aIsPrimarySort)
    {
      if (aSortKey == SortKeys.ID) // *2
        aQuery = aQuery.Sort(r => r.ID, aSortOrder, aIsPrimarySort); // *3
      else
      if (aSortKey == SortKeys.Caption) 
        aQuery = aQuery.Sort(r => r.Caption, aSortOrder, aIsPrimarySort);
      else
        ...
    }

  // private
    private class SortKeys
    {
      public const string ID = "ID";
      public const string Caption = "Caption";
      ...
    }      
}

关于我的问题

  

编译器如何解释OrderBy调用的确切原因   得到ORDER BY ID ASC代替ORDER BY %Value of ID% ASC?   编译器似乎以某种方式决定将s.ID推导为属性名称   ID而不是采用实际的属性数据类型,因此int   值。

我发现了一个有趣的blog和一个SO answer。基本上,它是通过钻入表达式主体以获取成员名称来完成的:

Expression<Func<int>> expression = () => Sample.Foo;
MemberExpression body = (MemberExpression)expression.Body;
string name = body.Member.Name;