【问题标题】:LINQ OrderBy with index ; getting error using Generic TKey带有索引的 LINQ OrderBy ;使用通用 TKey 出错
【发布时间】:2020-11-16 21:52:46
【问题描述】:

我正在尝试根据所选属性的索引订购IEnumerable<MyClass>

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

我有一个辅助方法来获取条件的Expression<>
它被编译以创建Func<> 以帮助订购。
我在尝试使 Func<> 返回要订购的通用 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&lt;TKey&gt; 来帮助订购?

【问题讨论】:

  • 糟糕,已将 Fun 更改为 Func。它只是放在OrderBy(...) 中的排序函数

标签: c# linq linq-to-entities expression-trees


【解决方案1】:

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

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

props[0] 中的属性是什么?所有属性都可读吗?

因此,如果您想获得明确的订单,我的建议是进行一些排序。例如获取按名称排序的公共可读属性。

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

  • 他们有一个类MyClass 和一个属性Date
  • 它们有一系列此类对象。
  • 他们想按属性订购Date
  • 他们必须确定属性Date 的索引,而不是按属性Date 或名为“日期”的属性排序:日期似乎是第四个属性
  • 然后他们必须使用以下索引调用您的方法:“OrderBy index 4”

如果他们只说:“按属性排序Date”或“按名为“日期”的属性排序不是更容易吗?

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

有时您的用户无权访问该属性,他们只知道该属性的名称。在这种情况下,您可以创建一个扩展方法,在其中提供属性的名称,并返回有序的源代码。

如果您不熟悉扩展方法。见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&lt;T&gt;, ...) 创建重载。

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

如果你真的想按索引排序,用 Lambda 表达式创建扩展方法会更容易,然后用表达式来摆弄。

考虑以下扩展方法:

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

【讨论】:

  • 感谢您的详细解释!我不知道订单没有定义。我想它会使用与类定义相同的顺序。按索引排序的原因是我能够从 javascript 客户端获得的所有信息。或者我可能需要额外的客户端处理来提取名称并将其传递给我的 MVC 控制器。
【解决方案2】:

为了能够对属性进行排序,它必须是实现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);
}

【讨论】:

    猜你喜欢
    • 2012-08-14
    • 1970-01-01
    • 2018-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-05
    • 2017-02-18
    • 1970-01-01
    相关资源
    最近更新 更多