2017-02-13 43 views
-1

我試圖讓generic method從下面的代碼段延長Array通用函數數組轉換爲KeyValuePair的排序列表(downvoted)

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,使用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 

或許有辦法OverloadArray.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 
+0

如果沒有**存儲** Func'' getProperty'來訪問包含對象'T' [...'array()as T']的屬性,那麼這是不可能的,它告訴哪個是'TKey'在轉換過程中創建'KeyValuePair' ...因此實現轉換的函數應該首先將'getProperty'存儲在一個變量中,該變量可以被**訪問**並從'ClassToPair'中調用。所以說它應該實現兩次:** 1。** store'getProperty',** 2。**'Array.ConvertAll' ... – rellampec

回答

0

我會爲我的問題提供一個答案。

雖然我不喜歡這種方法的清潔的原因(即該方法的類型參數太多),但它似乎是通過使用Extension Methods來解決它的唯一方法。

它具有使得能夠使用的Converter,圍繞該Delegate簽名引入了限制工作,通過存儲KeySelector成通用的,模塊變量的優點:Private accessKey As Object

我的猜測是最好的方法是實現一個完全泛型類,它不僅僅依賴於KeyValuePairs,而是具有更廣泛的轉換。

首先,在存儲KeySelector的目標,這將有助於一個List(Of TValue)ConvertAll一個List(Of KeyValuePair(Of TKey, TValue)),泛型類是必要的,這樣我們就可以存儲與沒有編譯錯誤一generic lambdareference)(似乎有不是有更好的方式來解決該限制的Converter Delegate介紹):

' to store a generic lambda: https://stackoverflow.com/a/3116009/6215377 
' 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重新調整,前提是getKey功能參數傳遞給toSortedPairedList存儲到generic模塊的對象accessKey As Object,在致電ConvertAllClassToPairConverter(將使用accessKey的那個)之前。這需要從Object一些type castings到類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: https://stackoverflow.com/q/2891797/6215377 (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: - https://stackoverflow.com/a/30151099/4352306 
    ' 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 

最後,這是測試,以顯示其用法:

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排序arrayslists

最佳希望

:所述排序方法被設計成它保留用於與相同的值的元素的原始插入順序。