【问题标题】:How do I order a collection based on a child property using LINQ or a Lambda?如何使用 LINQ 或 Lambda 基于子属性订购集合?
【发布时间】:2012-01-26 19:18:54
【问题描述】:

我得到以下 string 表达式:

"ChildObject.FullName" ...其中 ChildObject 是 MyObject1 类型的实例属性。

ChildObject 有一个名为“FullName”的属性,我想根据这个子属性“FullName”的值对“MyObject1”类型的集合进行排序。

我可以整天直接在 MyObject1 上的属性上执行此操作,但是在子实例上执行此操作时遇到了 2 个挑战,并且我无法让所有部分都正常工作。主要的两个挑战是:

  1. MyObject1 有几个不同的子属性类型,所以我不能硬编码 ChildObject 的类型。 字符串可以是任何类型。
  2. 排序表达式是字符串,不是已知类型。

对于上面的#2,我可以使用反射来获取子属性的类型,但我无法让它全部工作。我有以下内容,它可以编译并运行,但没有任何不同的排序:

'SortExpression below is a String like: "ChildObject.FullName"
MyObject1List = MyObject1List.OrderBy(Function(x)
      Dim t As Type = x.GetType()
      Dim tp As Type = t.GetProperty(SortExpression.Split(".").ElementAt(0)).PropertyType()
      Return tp.GetProperty(Request.CompareExpression.Split(".").ElementAt(1))
      End Function).ToList()

在表达式最后一行返回的值之上(如果我在 OrderBy 方法之外运行代码,确实为我提供了我需要的“全名”信息。所以代码必须接近,但是还是不行。

关于如何实现这一点的任何想法?我要防止的是 hardcoding 对子类型的一系列“If”块,然后将其类型硬编码到 sort 或 OrderBy 方法。

谢谢!

【问题讨论】:

  • 从基础对象派生您的对象,并在该基础对象中创建返回排序值的属性或方法。有点像 ToString() 但用于排序而不是字符串化。这样,您可以覆盖每种类型中的值,而无需按名称指定确切的属性。
  • 我实际上已经做了其中的一部分——它们派生自一个公共基类,该基类采用“T”类型并使用 IComparable,但遇到了同样的问题。如果可能的话,我希望得到上面这个小的 Lambda 表达式。
  • MyObject1List 中每个条目的 ChildObject 类型是否相同?

标签: .net vb.net linq lambda


【解决方案1】:

如果我正确理解了这个问题(免责声明 - 这是我写过的第一个 vb.net 代码,从语法上讲,它可能不是最好的 - 我首先用 c# 编写了它),实现这一目标的一种方法是执行以下操作...

假设您的 MyObject1 如下所示:

Public Class MyObject1

    Private mChildObject As SortableChildObject

    Public Property ChildObject() As SortableChildObject
        Get
            ChildObject = mChildObject
        End Get
        Set(value As SortableChildObject)
            mChildObject = value
        End Set
    End Property
End Class

注意它有一个必须是“SortableChildObject”的属性——这个类看起来像这样:

' Implement IComparable using reflection - just look up the property to
' sort on based on the "SortExpression" property 
Public MustInherit Class SortableChildObject
    Implements IComparable

    Protected MustOverride ReadOnly Property SortExpression() As String

    Public Function CompareTo(obj As Object) As Integer Implements System.IComparable.CompareTo

        ' Make sure the object we are comparing to is also our type
        Dim oo As SortableChildObject = TryCast(obj, SortableChildObject)
        If oo Is Nothing Then
            Throw New ArgumentException("I cannot compare these two objects")
        End If

        ' Get the value to sort on for this object
        Dim thisVal As IComparable = GetSortableValue(Me, SortExpression)
        If thisVal Is Nothing Then
            Throw New ArgumentException("Could not get the value of the sortable property for this")
        End If

        ' Get the value to sort on for the object we are comparing to
        Dim thatVal As IComparable = GetSortableValue(oo, oo.SortExpression)
        If thatVal Is Nothing Then
            Throw New ArgumentException("Could not get the value of the sortable property for that")
        End If

        ' Use the IComparable implementation of the properties we are comparing
        Return thisVal.CompareTo(thatVal)
    End Function

    Private Function GetSortableValue(obj As Object, sortExpression As String) As IComparable

        Dim prop As PropertyInfo = obj.GetType().GetProperty(sortExpression)
        If prop Is Nothing Then
            Throw New ArgumentException("Could not find the property " + sortExpression)
        End If

        Dim val As Object = prop.GetValue(obj, Nothing)

        Dim ret As IComparable = TryCast(val, IComparable)
        If ret Is Nothing Then
            Throw New ArgumentException("No way to compare the values as the comparable property does not implement IComparable")
        End If

        Return ret
    End Function
End Class

现在你必须做的是确保你想要排序的所有东西都继承自这个类,例如说我们有一个带有“FullName”字符串属性的东西,它看起来像这样:

' This is a child object that has a string property called "FullName" which
' is what we want to sort on
Public Class FullNameChildObject
    Inherits SortableChildObject

    Private mFullName As String

    Protected Overrides ReadOnly Property SortExpression() As String
        Get
            SortExpression = "FullName"
        End Get
    End Property

    Public Property FullName() As String
        Get
            FullName = mFullName
        End Get
        Set(value As String)
            mFullName = value
        End Set
    End Property

End Class

因此,要使用它,让我们构造一个小对象列表以进行排序:

Dim myObject1List As New List(Of MyObject1)

Dim i As FullNameChildObject = New FullNameChildObject
i.FullName = "B"

Dim o As New MyObject1
o.ChildObject = i
myObject1List.Add(o)

i = New FullNameChildObject
i.FullName = "A"
o = New MyObject1
o.ChildObject = i
myObject1List.Add(o)

i = New FullNameChildObject
i.FullName = "D"
o = New MyObject1
o.ChildObject = i
myObject1List.Add(o)

i = New FullNameChildObject
i.FullName = "C"
o = New MyObject1
o.ChildObject = i
myObject1List.Add(o)

然后根据 ChildObject 属性对其进行排序非常简单,您需要做的就是:

Dim ret = myObject1List.OrderBy(Function(x)
                                    Return x.ChildObject
                                End Function)

这是你需要的吗?这样做有点问题 - 正如您所看到的那样,有很多地方可能会出错 - 例如,如果您混合要比较的对象(例如,您有一个按整数排序的对象)另一个是字符串)它会抛出一个异常。

【讨论】:

    【解决方案2】:

    如果我理解正确,您有一个对象(例如,Parent),其中包含一个对象(例如,Child)。 Child 有一个 FullName 字段,您想按子 FullName 对父母列表进行排序。

    如果是这样,那么 OrderBy() 应该为您完成。

    假设我们有一个父母名单

    父母{ Id = 1,孩子 = { Id = 1,FullName = "Jan"}} 父母{ Id = 2,孩子 = { Id = 2,FullName = "Feb"}} 父母{ ID = 3,孩子 = { ID = 3,全名 =“Mar”}} 父{ ID = 4,子 = { ID = 4,全名 = "Apr"}}

    使用 OrderBy() 对它们进行排序

    Dim sorted = Items.OrderBy(Function(itm) itm.Child.FullName)
    

    给予

    父母{ Id = 4,孩子 = { Id = 4,FullName = "Apr"}} 父母{ Id = 2,孩子 = { Id = 2,FullName = "Feb"}} 父{ ID = 1,子 = { ID = 1,全名 =“Jan”}} Parent{ Id = 3, Child = { Id = 3, FullName = "Mar"}}

    (以下示例)

    hth,
    艾伦。

    编辑

    只需重新阅读问题。您有不同的按名称选择的 FullName 属性(来自查询字符串?)

    您可以使用通用形状的表达式(请参阅How can I create a dynamic Select on an IEnumerable<T> at runtime?)按名称选择属性。

    如果选定的属性是 IComparable(或者是 IEquatable?不太确定是哪个),那么 OrderBy() 仍然可以工作。这意味着只要排序字段是基本类型就可以了。如果它们是自定义类型(对象),您将需要做更多的工作......

    对第一个错误答案感到抱歉。

    更多编辑

    现在是星期五,这里很慢:?

    好的,扩展答案以按名称访问不同的子成员。 (我使用字段而不是属性,但两者都可以)。我们仍然需要知道字段的类型,但可能需要做更多的工作才能删除它(如果需要)。

    Private Class Child
    Public Id As Integer
    Public FullName As String
    End Class
    
    Private Class Parent
    Public Id As Integer
    Public Child As Child
    End Class
    
    Private Items As New List(Of Parent)() From { _
    New Parent() With { _
        Key .Id = 1, _
        Key .Child = New Child() With { _
            Key .Id = 1, _
            Key .FullName = "Jan" _
        } _
    }, _
    New Parent() With { _
        Key .Id = 2, _
        Key .Child = New Child() With { _
            Key .Id = 2, _
            Key .FullName = "Feb" _
        } _
    }, _
    New Parent() With { _
        Key .Id = 3, _
        Key .Child = New Child() With { _
            Key .Id = 3, _
            Key .FullName = "Mar" _
        } _
    }, _
    New Parent() With { _
        Key .Id = 4, _
        Key .Child = New Child() With { _
            Key .Id = 4, _
            Key .FullName = "Apr" _
        } _
    } _
    }
    
    <TestMethod> _
    Public Sub SortByChildName()
    Dim expectedParentIds = New () {4, 2, 1, 3}
    Dim sortedIds = Items.OrderBy(SelectExpression(Of Parent, String)("Child.FullName")).[Select](Function(itm) itm.Id)
    
    Assert.IsTrue(expectedParentIds.SequenceEqual(sortedIds))
    End Sub
    
    <TestMethod> _
    Public Sub SortByChildId()
    
    Dim expectedParentIds = New () {4, 3, 2, 1}
    Dim sortedIds = Items.OrderBy(SelectExpression(Of Parent, Integer)("Child.Id")).[Select](Function(itm) itm.Id)
    
    Assert.IsTrue(expectedParentIds.SequenceEqual(sortedIds))
    
    End Sub
    
    
    Public Shared Function SelectExpression(Of TItem, TField)(fieldNames As String) As Func(Of TItem, TField)
    
    Dim type = GetType(TItem)
    Dim fields = fieldNames.Split("."C)
    
    Dim arg As ParameterExpression = Expression.Parameter(type, "item")
    Dim expr As Expression = arg
    
    For Each field As String In fields
        Dim fieldInfo = type.GetField(field)
        expr = Expression.Field(expr, fieldInfo)
        type = fieldInfo.FieldType
    Next
    
    Return Expression.Lambda(Of Func(Of TItem, TField))(expr, arg).Compile()
    
    End Function
    

    【讨论】:

    • 您是否看到在调用SelectExpression 方法时,您如何传入“TField”值的硬编码类型(即字符串、整数)?这对我来说确实需要动态,否则我在拨打电话时必须有一个巨大的Select Case 声明。我实际上已经有了 Reflected 类型,但该值不适用于“TField”。关于如何传递类型而不是硬编码的任何想法?
    • @atconway 问题是 SelectExpression 返回一个 Lambda(Of Func(Of Item, TField))。我不知道这是否可以参数化。如果有机会,我会看看,但不能保证什么时候有机会。对不起。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-16
    • 2021-01-27
    相关资源
    最近更新 更多