LINQ OrderBy 索引;使用通用 TKey 出错

LINQ OrderBy with index ; getting error using Generic TKey

我正在尝试根据所选 属性:

的索引订购 IEnumerable<MyClass>
class MyClass 
{
 public string Name;
 public string Value;
}

我有一个辅助方法来获取 Expression<> 的条件。
它被编译以创建 Func<> 来帮助排序。
我在尝试使 Func<> return 成为要订购的通用 TKey 时遇到问题。

public Expression<Func<T, TKey>> Helper<T>(int propIdx)
{
    var param = Expression.Parameter(typeof(T), "record");
    var props = typeof(T).GetProperties();

    if(propIdx > props.Count()) 
        return Expression.Lambda<Func<T, TKey>>)(Expression.Constant(true), param);

    Expression propExp = Expression.Property(param, props[propIdx].Name);

    return lambda = Expression.Lambda<Func<T, TKey>>(propExp, param);
}

这用于帮助动态订单标准。

OrderByIdx(IEnumerable<MyClass> input, int propIdx)
{
     Expression<Func<T, TKey>> exp = Helper(propIdx);
     
     var funct = exp.Compile();

     return input.OrderBy(funct);
}

我遇到错误 The type or namespace TKey could not be found

如何使用 Generic<TKey> 来帮助订购?

要能够在 属性 上订购,它必须是实现 IComparable 的类型。您可以使用此事实在您的代码中将 TKey 替换为 IComparable

class MyClass 
{
 public string Name { get; set; }
 public string Value { get; set; }
}

public Expression<Func<T, IComparable>> Helper<T>(int propIdx)
{
    var param = Expression.Parameter(typeof(T), "record");
    var props = typeof(T).GetProperties();

    Expression propExp = Expression.Property(param, props[propIdx].Name);

    return Expression.Lambda<Func<T, IComparable>>(propExp, param);
}

IEnumerable<MyClass> OrderByIdx(IEnumerable<MyClass> input, int propIdx)
{
     Expression<Func<MyClass, IComparable>> exp = Helper<MyClass>(propIdx);
     
     var funct = exp.Compile();

     return input.OrderBy(funct);
}

首先:Type.GetProperties没有明确的顺序。

var props = typeof(T).GetProperties();

哪个 属性 会在 props[0] 中?所有属性都可读吗?

因此,如果您想获得定义的顺序,我的建议是进行一些排序。例如获取按名称排序的 public 可读属性。

此外,如果您的代码的用户(= 软件,而不是操作员)想要使用基于索引的通用代码,这不是有点奇怪吗?

  • 他们有 class MyClass 和 属性 Date
  • 他们有这个 class 的对象序列。
  • 他们想在 属性 Date
  • 之前订购
  • 不是按 属性 Date 或按 属性 命名为“日期”的顺序,而是必须确定 属性 Date 的索引:日期好像是第四个属性
  • 然后他们必须使用此索引调用您的方法:“OrderBy 索引 4”

如果他们可以直接说:“按 属性 Date 订购”或“按名为 'Date' 的 属性 订购,岂不是更容易? ?

IEnumerable<MyClass> source = ...
var orderedSource = source.OrderBy(t => t.Date);

有时您的用户无法访问 属性,他们只知道 属性 的名称。在这种情况下,您可以创建一个扩展方法,在其中提供 属性 的名称,并且 returns 订购的源。

如果您不熟悉扩展方法。参见 extension methods demystified

用法类似于:

var orderedSource = source.OrderBy(nameof(MyClass.Date));

或者例如:“按我的 DataGridView 中选定的列排序”。

string propertyName = this.DataGridView.Columns.Cast<DataGridViewColumn>()
    .Where(column => column.IsSelected)
    .Select(column => column.DataPropertyName)
    .FirstOrDefault();
var orderedSource = source.OrderBy(propertyName);

这样的程序容易制作,易于使用和重用,易于测试,易于维护。最重要的是:他们是单线的:

public static IOrderedEnumerable<T> OrderBy<T>(
    this IEnumerable<T> source,
    string propertyName)
{
    // TODO: handle invalid input
    PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName);
    // TODO: handle invalid propertyname

    return source.OrderBy(propertyInfo);
}

public static IOrderedEnumerable<T> OrderBy<T>(
    this IEnumerable<T> source,
    PropertyInfo propertyInfo)
{
    // TODO: handle invalid parameters
    return source.OrderBy(t => propertyInfo.GetValue(t) as propertyInfo.PropertyType);
}

如果需要,您可以为 ThenBy(this IEnumerable<T>, ...).

创建重载

但我想按索引排序,而不是按名称排序!

如果您真的想按索引排序,使用 Lambda 表达式创建扩展方法更容易,然后 fiddle 使用表达式。

考虑以下扩展方法:

public static IOrderedEnumerable<T> OrderBy<T>(
    IEnumerable<T> source,
    int propertyIndex)
{
    return source.OrderBy(propertyIndex, GetDefaultPropertyOrder(typeof(T));
}

public static IOrderedEnumerable<T> OrderBy<T>(
    IEnumerable<T> source,
    int propertyIndex,
    IReadOnlyList<PropertyInfo> properties)
{
    // TODO: handle null and out-of-range parameters
    PropertyInfo sortProperty = properties[propertyIndex];
    return source.OrderBy(sortProperty);
}

public static IList<PropertyInfo> GetDefaultPropertyOrder(Type t)
{
    return t.GetProperties()
        .Where(property => property.CanRead)
        .OrderBy(property => property.Name)
        .ToList();
}

用法:

BindingList<MyClass> myObjects = ...

// display myObjects in a dataGridView
this.DataGridView.DataSource = myObjects;

// event if operator clicks on column header:
public void OnColumnHeaderClicked(object sender, ...)
{
    DataGridViewColumn clickedColumn = GetClickedColumn();

    // sort by column index, as you prefer:
    var sortedObjects = myObjects.SortBy(clickedColumn.DisplayIndex);
    ProcessSortedObjects(sortedObjects);

    // but of course, you skip some intermediate methods if you sort by property name
    var sortedObject = myObject.SortBy(clickedColumn.DataPropertyName);
    ProcessSortedObjects(sortedObjects);
}