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

How is generic TKey interpreted in C# when using lambda notation?

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

当前基地class代码:

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
}

编译器输出错误

Cannot implicitly convert type 'int' to 'TKey'

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

  2. 如何解决编译错误?

提前致谢。

编辑

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

  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.ID 推断为 property name "ID",而不是采用实际的 属性 数据类型,从而得到 int 值。

编辑2

好的,+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);
}

How does OrderBy infer what the types of TSource and TKey are

编译器可以通过查看以下内容来推断 TSourceTKey 的类型:

  1. lQuery 的类型 - 在这种情况下,IQueryable<{your class}>
  2. return 表达式的类型,在本例中,ID 属性 of {your class}
  3. 的类型

How to resolve the compiler error?

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

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

  aQuery.OrderBy(lKeySelector);
}

但请注意 OrderBy returns 查询 - 它不会 更改 原始查询,所以你真正想要的是:

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 参数 - 最好 return 一个新值而不是更改原始值。

我想我以一种没有人理解我的意图的方式写下了我的问题。我的目标是避免为每个需要排序的字段重复编写相同的 .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;
}    

基础class

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

派生class

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

关于我的问题

How exactly is that OrderBy call interpreted by the compiler as it results in an ORDER BY ID ASC instead of ORDER BY %Value of ID% ASC? The compiler seems to somehow decide to deduce s.ID to property name ID instead of taking the actual property data type and thus the int value.

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

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