将 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
并创建 Delegate
到 Converter
的替代方法,并带有允许完成以下代码的签名(显然不要为 ConvertAll
和 AddressOf 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
重新适配,前提是作为参数传递给 toSortedPairedList
的 getKey
函数存储到模块的 generic
对象中,accessKey As Object
,然后用 ClassToPair
调用 ConvertAll
作为 Converter
(将使用 accessKey
的那个)。这将需要一些 type castings
从 Object
到 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 lambdas
对 arrays
和 lists
.[=42 进行排序时克服类型转换困难的方法=]
祝福
注意:排序方法被设计为保留具有相同值的元素的原始插入顺序。
我正在尝试从以下代码中获取 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
并创建 Delegate
到 Converter
的替代方法,并带有允许完成以下代码的签名(显然不要为 ConvertAll
和 AddressOf 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
重新适配,前提是作为参数传递给 toSortedPairedList
的 getKey
函数存储到模块的 generic
对象中,accessKey As Object
,然后用 ClassToPair
调用 ConvertAll
作为 Converter
(将使用 accessKey
的那个)。这将需要一些 type castings
从 Object
到 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 lambdas
对 arrays
和 lists
.[=42 进行排序时克服类型转换困难的方法=]
祝福
注意:排序方法被设计为保留具有相同值的元素的原始插入顺序。