任意のオブジェクトを任意のプロパティに基いてソート

概要

例えばHumanという型があって、Id,Name,AgeというPublicプロパティを持っていた時、List(Of Human)をHumanの任意のプロパティに基いてソートを行いときがある。この処理を行いたいとき、List(Of T)のSortメソッドでは引数として任意のComparison(Of T)やIComparer(Of T)を渡すことで、独自の並び替え方法を提供することができる。これを利用しない手はない。

しかし何種類ものソート方法を提供したいとき、その数にあわせてComparisonまたはIComparerを実装する必要があり、なかなか手間のかかる作業だ。そこでリフレクションを用いて任意の型Tの任意のパブリックプロパティに基いてComparisonを自動生成するクラスを作成した。

機能説明

任意の型Tのプロパティ名を指定することで新しいComarisonを作成できる。指定したプロパティがComparableを実装していれば、そのComparableのComparaToを用いて比較を行う。指定したプロパティがComparableを実装していなければ、そのプロパティを一旦Stringに変換してComparaToで比較を行う。

ソースコード


Imports System.Reflection

''' <summary>
''' 任意のオブジェクトTを任意のプロパティに基いてソートするためのComparisonを生成するクラス
''' </summary>
''' <remarks></remarks>
Public Class ComparisonCreater(Of T)

#Region "Comparisonの取得メソッド"

    ''' <summary>
    ''' Tを指定したプロパティに基いて比較するためのComparisonを返す
    ''' </summary>
    ''' <param name="sortType">降順、昇順を選択可能</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetComparison(ByVal propName As String, ByVal sortType As SortType) As Comparison(Of T)
        If propName Is Nothing Then Throw New ArgumentNullException
        Select Case sortType
            Case sortType.Asc
                Return GetPropAscComparison(propName)
            Case sortType.Desc
                Return GetPropDescComparison(propName)
            Case sortType.None
                Throw New ArgumentException
            Case Else
                Throw New InvalidOperationException
        End Select
        Return Nothing
    End Function

    ''' <summary>
    ''' Tを指定したプロパティ(昇順)に基いて比較するためのComparisonを返す
    ''' 
    ''' 指定したプロパティがIComparableを実装している場合それを利用して比較する
    ''' IComparableを実装していない場合、Stringに変換して比較する
    ''' </summary>
    ''' <param name="propName">Tのプロパティ名</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overridable Function GetPropAscComparison(ByVal propName As String) As Comparison(Of T)
        Dim prop As PropertyInfo = GetProp(propName)
        If prop Is Nothing Then Return Nothing
        Dim type As Type = prop.GetGetMethod.ReturnType

        'IComparableを実装しているか
        If Not type.GetInterface("IComparable") Is Nothing Then
            '実装しているとき
            Return Function(x, y) _
                        DirectCast(prop.GetValue(y, Nothing), IComparable).CompareTo( _
                            DirectCast(prop.GetValue(x, Nothing), IComparable))
        Else
            '実装してないときはStringに変換して比較
            Return Function(x, y) prop.GetValue(y, Nothing).ToString.CompareTo( _
                       prop.GetValue(x, Nothing).ToString)
        End If
    End Function

    ''' <summary>
    ''' Tを指定したプロパティ(降順)に基いて比較するためのComparisonを返す
    ''' </summary>
    ''' <param name="propName">Tのプロパティ名</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overridable Function GetPropDescComparison(ByVal propName As String) As Comparison(Of T)
        Dim comp As Comparison(Of T) = GetPropAscComparison(propName)
        Return ReverseComp(comp)
    End Function

    Protected Function ReverseComp(ByVal comp As Comparison(Of T)) As Comparison(Of T)
        If comp Is Nothing Then Throw New ArgumentNullException
        Return Function(x, y) comp(y, x)
    End Function

#End Region


#Region "基本的なリフレクションの処理"

    ''' <summary>
    ''' 指定された型Tのパブリックプロパティの数を数える
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Protected Function PropCount() As Integer
        Dim type As Type = GetType(T)
        Dim count As Integer = 0
        For Each prop As PropertyInfo In type.GetProperties
            If Not prop.GetGetMethod(False) Is Nothing Then
                count += 1
            End If
        Next
        Return count
    End Function

    ''' <summary>
    ''' 指定されたプロパティがTに含まれるか
    ''' </summary>
    ''' <param name="propName">Tのプロパティ名</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Protected Function ContainsProp(ByVal propName As String, Optional ByVal nonPublic As Boolean = False) As Boolean
        Dim prop As PropertyInfo = GetProp(propName)
        If prop Is Nothing Then Return False
        Return If(prop.GetGetMethod(nonPublic) Is Nothing, False, True)
    End Function

    ''' <summary>
    ''' Tから指定したプロパティを取得する
    ''' </summary>
    ''' <param name="propName">Tのプロパティ名</param>
    ''' <returns>指定したプロパティが無ければnull</returns>
    ''' <remarks></remarks>
    Protected Function GetProp(ByVal propName As String) As PropertyInfo
        Dim type As Type = GetType(T)
        Dim prop As PropertyInfo = type.GetProperty(propName)
        Return prop
    End Function

#End Region

End Class


Public Enum SortType
    None
    Asc
    Desc
End Enum

使い方

        Dim creater As New ComparisonCreater(Of Human)()
        Dim comp As Comparison(Of T) = creater.GetComparison("Name", SortType.Asc)

        Dim list as new List(of T)  '適当な値が入ってるとする
        list .Sort(comp)