将 Array 转换为排序的 KeyValuePair 列表的通用函数(已投票)

Generic function to convert Array to sorted List of KeyValuePair (downvoted)

我正在尝试从以下代码中获取 generic method 以扩展 Array

Public Class clsField
    Public idx As String
    Public name As String
    Public weight As Long

    Public Sub New(i As String, n As String, w As Long)
        idx = i : name = n : weight = w
    End Sub
End Class

Public Class Container
    Public fields As clsField() ' filled in by a JSON parser (order matters)

    ' returns a list sorted by clsField.weight preserving order for elements with same 'weight' value
    Public Function getFields() As List(Of KeyValuePair(Of String, clsField))
        Dim auxList As List(Of KeyValuePair(Of String, clsField))

        If (fields Is Nothing) OrElse (fields.Count < 1) Then Return New List(Of KeyValuePair(Of String, clsField))
        ' .ToList to transform IEnumerable to the return type
        auxList = Array.ConvertAll(fields, New Converter(Of clsField, KeyValuePair(Of String, clsField))(AddressOf FieldToPair)).ToList
        Return auxList.OrderBy(Function(x) x.Value.weight).ToList()
    End Function

    Public Shared Function FieldToPair(fld As clsField) As KeyValuePair(Of String, clsField)
        Return New KeyValuePair(Of String, clsField)(fld.idx, fld)
    End Function
End Class

我坚持使用 Converter(Of TInput, TOutput) Delegate, used by Array.ConvertAll,它不接受新参数,前提是我可以传递一个函数来指定应该在 TInput 上使用的 key

Private Function ClassToPair(Of T)(obj As T, getProperty As Func(Of T, Object)) As KeyValuePair(Of String, T)
    Return New KeyValuePair(Of String, T)(getProperty(obj), obj)
End Function

也许有一种方法可以 Overload Array.ConvertAll 并创建 DelegateConverter 的替代方法,并带有允许完成以下代码的签名(显然不要为 ConvertAllAddressOf ClassToPair 编译;在此处添加以反映想法):

Module ArrayExtension ' custom method for array
    ' returns a list sorted by clsField.weight preserving order for elements with same 'weight' value
    ' getKey is used to transform the array into a List (Of KeyValuePair (Of String, T)) -> using the Converter
    ' getSortProperty is used to change the sorting 'property'
    <Extension()>
    Public Function toSortedPairedList(Of T)(arr As T(), Optional getKey As Func(Of T, String) = Nothing,
           Optional getSortProperty As Func(Of KeyValuePair(Of String, T), Object) = Nothing) _
           As List(Of KeyValuePair(Of String, T))
        Dim auxList As List(Of KeyValuePair(Of String, T))

        If (arr Is Nothing) OrElse (arr.Count < 1) Then Return New List(Of KeyValuePair(Of String, T))

        ' .ToList to transform IEnumerable to the return type
        auxList = Array.ConvertAll(arr, New Converter(Of T, KeyValuePair(Of String, T))(AddressOf ClassToPair)).ToList
        Return auxList.OrderBy(getSortProperty).ToList()

    End Function

    Private Function ClassToPair(Of T)(obj As T, getProperty As Func(Of T, Object)) As KeyValuePair(Of String, T)
        Return New KeyValuePair(Of String, T)(getProperty(obj), obj)
    End Function
End Module

所以,无法将 getKey 函数传递给转换器...

对于第一个示例,它的用法类似于:

Public Function getFields() As List(Of KeyValuePair(Of String, clsField))
    Dim auxList As List(Of KeyValuePair(Of String, clsField))

    If (fields Is Nothing) OrElse (fields.Count < 1) Then Return New List(Of KeyValuePair(Of String, clsField))

    Return fields.toSortedPairedList(Function(x) x.idx, Function(y) y.Value.weight)
End Function

我会回答我的问题。

虽然出于简洁的原因我不喜欢这种方法(即方法的类型参数太多),但它似乎是使用 Extension Methods 解决它的唯一方法。

它的优点是可以使用 Converter,解决 Delegate 签名引入的限制,方法是将 KeySelector 存储到通用模块变量中: Private accessKey As Object.

我的猜测是,最好的方法是实现一个完整的通用 class,它不仅依赖于 KeyValuePairs,而且具有更广泛的转换集。

首先,为了存储KeySelector一个List(Of TValue)ConvertAll一个List(Of KeyValuePair(Of TKey, TValue)),一个通用的 class 是必要的,因此我们可以存储一个没有编译错误的 generic lambda (reference)(似乎没有更好的方法来解决限制Converter Delegate介绍):

' to store a generic lambda: 
' the class shows up to be necessary, as it cannot be stored by the use of generic delegates 
' otherwise, you will always end up having to instantiate to specific types for KeyValuePair, 
' losing so the generic declaration
Interface IAccesser(Of TValue, TKey)
    Function accessProperty(input As TValue) As TKey
End Interface

Public Class PropertyAccessor(Of TValue, TKey)
    Implements IAccesser(Of TValue, TKey)
    Private pFunc As Func(Of TValue, TKey)

    Public Sub New(f As Func(Of TValue, TKey))
        pFunc = f
    End Sub
    Function accessProperty(o As TValue) As TKey Implements IAccesser(Of TValue, TKey).accessProperty
        Return pFunc(o)
    End Function
End Class

现在 Extension Module 重新适配,前提是作为参数传递给 toSortedPairedListgetKey 函数存储到模块的 generic 对象中,accessKey As Object,然后用 ClassToPair 调用 ConvertAll 作为 Converter(将使用 accessKey 的那个)。这将需要一些 type castingsObject 到 class PropertyAccessor:

Imports System.Linq.Enumerable
Imports System.Runtime.CompilerServices ' for extensions

Module ArrayExtension  ' custom method for array
    Private accessKey As Object ' to store the generic lambda

    Private Function ClassToPair(Of TValue, TKey)(obj As TValue) As KeyValuePair(Of TKey, TValue) ' the Converter
        ' this is the one that avoids to mess around with delegates (as instances of delegates cannot be generic)
        Dim a As PropertyAccessor(Of TValue, TKey) = DirectCast(accessKey, PropertyAccessor(Of TValue, TKey)) 
        Return New KeyValuePair(Of TKey, TValue)(a.accessProperty(obj), obj)
    End Function

    <Extension()> ' the type params list gets long, as we target it to be generic
    Public Function toSortedPairedList(Of TValue, TKey, TSort, TReturn)(arr As TValue(), getKey As Func(Of TValue, TKey),
           Optional getSortProperty As Func(Of KeyValuePair(Of TKey, TValue), TSort) = Nothing) _
           As List(Of KeyValuePair(Of TKey, TValue))

        If (getKey Is Nothing) OrElse (arr Is Nothing) OrElse (arr.Count < 1) Then Return New List(Of KeyValuePair(Of TKey, TValue)) ' empty list (instead of nothing)
        Dim a As PropertyAccessor(Of TValue, TKey) = New PropertyAccessor(Of TValue, TKey)(getKey)
        accessKey = a ' here we store / assign, so we can use it within the Converter function ClassToPair (with the delegate signature that introduced the problem)

        ' Typecasting Generic parameter:  (can throw an exception; i.e. TSort = Integer, TKey = non-numeric String)
        ' NOTE: this part is not essential (just an improvement)
        ' NOTE II: we leave the Exception Catch to the caller (an improvement would be to throw an adapted Exception from here)
        If getSortProperty Is Nothing Then getSortProperty = Function(x) CType(CObj(a.accessProperty(x.Value)), TSort) ' defaulting to sort by getKey(obj)

        Dim auxList As List(Of KeyValuePair(Of TKey, TValue))
        auxList = Array.ConvertAll(arr, New Converter(Of TValue, KeyValuePair(Of TKey, TValue))(AddressOf ClassToPair(Of TValue, TKey))).ToList()
        Return auxList.OrderBy(getSortProperty).ToList()  ' .ToList to transform IEnumerable to the return type
    End Function

    ' Array Extension: -  
    ' irrelevant to this question (to Push into array)
    <Extension()>
    Public Sub Add(Of T)(ByRef arr As T(), item As T)
        If arr IsNot Nothing Then
            Array.Resize(arr, arr.Length + 1)
            arr(arr.Length - 1) = item
        Else
            ReDim arr(0)
            arr(0) = item
        End If
    End Sub
End Module

最后,这是 tester,以展示其用法:

Public Class clsField
    Public idx As String
    Public name As String
    Public weight As Long

    Public Sub New(i As String, n As String, w As Long)
        idx = i : name = n : weight = w
    End Sub

    Public Overrides Function ToString() As String
        Return String.Format("{0}: {1} - {2}", name, idx, weight)
    End Function
End Class

Public Class Container
    Public fields() As clsField

    ' here we call the extended method
    Public Function getSortedPairs() As List(Of KeyValuePair(Of String, clsField))
        Return fields.toSortedPairedList(Of String, Long, List(Of KeyValuePair(Of String, clsField)))(Function(f) f.idx, Function(f) f.Value.weight)
    End Function

    ' it calls to the function above and converts back to List(Of clsField)
    ' NOTE: not necessary; added to show more ideas of its usability
    Public Function getSortedFields() As List(Of clsField) 
        Return getSortedPairs.ConvertAll(Function(pair) pair.Value)
    End Function
End Class

Public Class Consumer
    Public cont As Container

    Public Sub New()
        cont = New Container
        cont.fields.Add(New clsField("ffq", "foo30004", 33))
        cont.fields.Add(New clsField("ffc", "foo9997", 55))
        cont.fields.Add(New clsField("ffp", "foo9908", 55))
        cont.fields.Add(New clsField("ffo", "foo100001", 22))
        cont.fields.Add(New clsField("ffx", "foo8885", 33))
        cont.fields.Add(New clsField("ffz", "foo70002", 22))
        cont.fields.Add(New clsField("ffy", "foo8806", 33))
        cont.fields.Add(New clsField("ffa", "foo9009", 55))
        cont.fields.Add(New clsField("ffb", "foo8000", 55))
        cont.fields.Add(New clsField("ffn", "foo7003", 22))
    End Sub

    Public Sub printSortedFields()
        For Each e As clsField In cont.getSortedFields()
            Console.WriteLine(e.ToString())
            Debug.Print(e.ToString())
        Next
    End Sub

    Public Sub Main()
        printSortedFields()
    End Sub
End Class

测试写入此输出(创建的条目因此名称的最后一位确认测试):

foo100001: ffo - 22
foo70002: ffz - 22
foo7003: ffn - 22
foo30004: ffq - 33
foo8885: ffx - 33
foo8806: ffy - 33
foo9997: ffc - 55
foo9908: ffp - 55
foo9009: ffa - 55
foo8000: ffb - 55

希望这对遇到类似问题的人有所帮助。虽然我不认为这是一个确定的解决方案,但它提供了一些指导,可以采取或放弃在使用 generic methods and lambdasarrayslists.[=42 进行排序时克服类型转换困难的方法=]

祝福

注意排序方法被设计为保留具有相同值的元素的原始插入顺序。