如何使用LINQ或Lambda基于子属性订购集合?

时间:2012-01-26 19:18:55

标签: .net vb.net linq lambda

我提供了以下字符串表达式:

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

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

我可以直接在MyObject1上对属性进行整天这样做,但是当我在子实例上进行操作时遇到2个挑战并且我无法使所有部分工作。主要的2个挑战是:

  1. MyObject1有一些不同的子属性类型,因此我无法对ChildObject的类型进行硬编码。 字符串可以是任何类型。
  2. sort表达式是String而不是已知类型。
  3. 对于上面的#2,我可以使用Reflection来获取子属性的类型,但我无法全部工作。我有下面的内容,它编译并运行,但不会有任何不同的排序:

    '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方法, 为我提供了我需要的'FullName'信息。所以代码必须关闭,但它仍然不起作用。

    关于如何实现这一目标的任何想法?我想要阻止的是硬编码子类型上的一系列“If”块,然后将其类型硬编码为sort或OrderBy方法。

    谢谢!

2 个答案:

答案 0 :(得分:2)

如果我正确地理解了这个问题(免责声明 - 这是我写过的第一个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)

这就是你需要的吗?这样做有点问题 - 正如你所看到的那样,有很多地方可能会出现问题 - 例如,如果你混合了你想要比较的对象(例如,你有一个对整数进行排序的对象)它将引发异常。

答案 1 :(得分:1)

如果我理解正确,你有一个对象(比如,Parent),它包含一个对象(比如Child)。 Child有一个FullName字段,您希望按子FullName对父项列表进行排序。

如果是,那么 OrderBy()应该为你做。

假设我们有一个父母列表

父{Id = 1,Child = {Id = 1,FullName =“Jan”}} 父{Id = 2,Child = {Id = 2,FullName =“Feb”}} 父{Id = 3,Child = {Id = 3,FullName =“Mar”}} 父{Id = 4,Child = {Id = 4,FullName =“Apr”}}

使用OrderBy()

对它们进行排序
Dim sorted = Items.OrderBy(Function(itm) itm.Child.FullName)

给出

父{Id = 4,Child = {Id = 4,FullName =“Apr”}} 父{Id = 2,Child = {Id = 2,FullName =“Feb”}} 父{Id = 1,Child = {Id = 1,FullName =“Jan”}} 父{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()仍然可以工作。这意味着只要排序字段是基本类型就可以了。如果它们是自定义类型(对象),您将需要做更多的工作......

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

更多编辑

这是星期五,在这里缓慢:?

好的,扩大了答案,通过名字访问不同的儿童成员。 (我使用的是Fields而不是Properties,但它们都可以使用)。我们仍然需要知道字段的类型,但可能会删除更多的工作(如果需要)。

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