ここでは基本型のデフォルトのソート順について見てみます。 int, long等の数値型やDateTime等の型はIComparableインターフェイスを実装していて、デフォルトではそれによって定義される順で並べ替えが行われます。 数値型では数の小さい順、文字列型では辞書順、日付型では日付の古い順での並べ替えが基本となっています。

数値型

int, long, uint, byte等の整数型、float, double, decimal等の実数型は、いずれもその数の小さい順に並べ替えられます。

実行結果
int: -2147483648, -1, 0, 1, 2147483647
decimal: -79228162514264337593543950335, -1, -0.01, 0, 0.01, 1, 79228162514264337593543950335
数値型の値のソート順序
using System;

class Sample {
  static void Main()
  {
    var intArray = new int[] {0, 1, -1, int.MaxValue, int.MinValue};
    var decimalArray = new decimal[] {
      0m, 1m, -1m, 0.01m, -0.01m,
      decimal.MaxValue, decimal.MinValue,
    };

    Array.Sort(intArray);
    Array.Sort(decimalArray);

    Console.Write("int: ");
    Console.WriteLine(string.Join(", ", intArray));

    Console.Write("decimal: ");
    Console.WriteLine(string.Join(", ", decimalArray));
  }
}
数値型の値のソート順序
Imports System

Class Sample
  Shared Sub Main()
    Dim intArray As Integer() = New Integer() {0, 1, -1, Integer.MaxValue, Integer.MinValue}
    Dim decimalArray As Decimal() = New Decimal() { _
      0D, 1D, -1D, 0.01D, -0.01D, _
      Decimal.MaxValue, Decimal.MinValue _
    }

    Array.Sort(intArray)
    Array.Sort(decimalArray)

    Console.Write("Integer: ")
    Console.WriteLine(String.Join(", ", intArray))

    Console.Write("Decimal: ")
    Console.WriteLine(String.Join(", ", decimalArray))
  End Sub
End Class

浮動小数点型

float, doubleの場合、NaN(非数)は-∞(負の無限大)や他の数よりも小さいと扱われる点に注意が必要です。

実行結果
double: NaN, -Infinity, -1.79769313486232E+308, -1, -0.01, 0, 0.01, 1, 1.79769313486232E+308, Infinity
浮動小数点型の値のソート順序
using System;

class Sample {
  static void Main()
  {
    var doubleArray = new double[] {
      0.0, 1.0, -1.0, 0.01, -0.01,
      double.MaxValue, double.MinValue,
      double.PositiveInfinity, double.NegativeInfinity, double.NaN,
    };

    Array.Sort(doubleArray);

    Console.Write("double: ");
    Console.WriteLine(string.Join(", ", doubleArray));
  }
}
浮動小数点型の値のソート順序
Imports System

Class Sample
  Shared Sub Main()
    Dim doubleArray As Double() = New Double() { _
      0.0, 1.0, -1.0, 0.01, -0.01, _
      double.MaxValue, double.MinValue, _
      double.PositiveInfinity, double.NegativeInfinity, double.NaN _
    }

    Array.Sort(doubleArray)

    Console.Write("Double: ")
    Console.WriteLine(string.Join(", ", doubleArray))
  End Sub
End Class

ヌル許容の数値型

ヌル許容の数値型の場合、null/Nothingは他のどの数よりも小さいと扱われます。

実行結果
(null), -2147483648, -1, 0, 1, 2147483647,
ヌル許容の整数型における値のソート順序
using System;

class Sample {
  static void Main()
  {
    var arr = new int?[] {0, 1, -1, null, int.MaxValue, int.MinValue};

    Array.Sort(arr);

    foreach (var val in arr) {
      Console.Write("{0}, ", val == null ? "(null)" : val.ToString());
    }
    Console.WriteLine();
  }
}
ヌル許容の整数型における値のソート順序
Imports System

Class Sample
  Shared Sub Main()
    Dim arr As Integer?() = New Integer?() {0, 1, -1, Nothing, Integer.MaxValue, Integer.MinValue}

    Array.Sort(arr)

    For Each val As Integer? In arr
      If val Is Nothing Then
        Console.Write("{0}, ", "(Nothing)")
      Else
        Console.Write("{0}, ", val)
      End If
    Next
  End Sub
End Class

またNaNに対しても同様で、null/NothingはNaNよりも小さいと扱われます。 つまり、小さい順位並べるとnull < NaN < -∞ < MinValue < 0 の順になります。

実行結果
(null), NaN, -Infinity, -1.79769313486232E+308, -1, 0, 1,
ヌル許容の数値型におけるnull、NaN、-∞のソート順序
using System;

class Sample {
  static void Main()
  {
    var arr = new double?[] {
      0.0, 1.0, -1.0, null, double.MinValue, double.NegativeInfinity, double.NaN,
    };

    Array.Sort(arr);

    foreach (var val in arr) {
      Console.Write("{0}, ", val == null ? "(null)" : val.ToString());
    }
    Console.WriteLine();
  }
}
ヌル許容の数値型におけるnull、NaN、-∞のソート順序
Imports System

Class Sample
  Shared Sub Main()
    Dim arr As Double?() = New Double?() { _
      0.0, 1.0, -1.0, Nothing, _
      double.MinValue, double.NegativeInfinity, double.NaN _
    }

    Array.Sort(arr)

    For Each val As Double? In arr
      If val Is Nothing Then
        Console.Write("{0}, ", "(Nothing)")
      Else
        Console.Write("{0}, ", val)
      End If
    Next
    Console.WriteLine()
  End Sub
End Class

文字列型

文字列型では、辞書順(文字の順、長さの短い順)に並べ替えられます。

実行結果
a
aa
aaa
aaaa
aab
aac
ab
aba
abb
ac
文字列型の値のソート順序
using System;

class Sample {
  static void Main()
  {
    var arr = new string[] {
      "a", "aa", "ab", "ac", "aaa", "aab", "aba", "abb", "aac", "aaaa",
    };

    Array.Sort(arr);

    foreach (var val in arr) {
      Console.WriteLine(val);
    }
  }
}
文字列型の値のソート順序
Imports System

Class Sample
  Shared Sub Main()
    Dim arr As String() = New String() { _
      "a", "aa", "ab", "ac", "aaa", "aab", "aba", "abb", "aac", "aaaa" _
    }

    Array.Sort(arr)

    For Each val As String In arr
      Console.WriteLine(val)
    Next
  End Sub
End Class

ただ、カルチャや比較方法によっては単なる辞書順にはならないので注意してください。 詳しくは文字列と比較オプション・カルチャの並べ替え規則カルチャと書式・テキスト処理・暦で解説しています。


null/Nothing空の文字列(長さ0の文字列)が含まれていても並べ替えることができます。 null/Nothingは、空の文字列を含む他のどの文字列よりも小さいと扱われます。 つまり、空の文字列を含めて大小関係を並べると、「null/Nothing < 空の文字列 < 1文字以上の文字列」の順となります。

実行結果
(null)

a
aa
ab
b
文字列型におけるnull、空の文字列のソート順序
using System;

class Sample {
  static void Main()
  {
    var arr = new string[] {
      "a", "aa", "ab", "b", null, ""
    };

    Array.Sort(arr);

    foreach (var val in arr) {
      Console.WriteLine(val ?? "(null)");
    }
  }
}
文字列型におけるnull、空の文字列のソート順序
Imports System

Class Sample
  Shared Sub Main()
    Dim arr As String() = New String() { _
      "a", "aa", "ab", "b", Nothing, "" _
    }

    Array.Sort(arr)

    For Each val As String In arr
      Console.WriteLine(If(val, "(Nothing)"))
    Next
  End Sub
End Class

大文字小文字の違いを考慮したソート

文字列をソートする際、場合によっては大文字小文字の違いをどう扱うか厳密に決める必要が出て来ます。 その場合は、StringComparerを使ってどう扱うかを指定することができます。 Sortメソッド、OrderByメソッドともにIComparerを引数に取ることができ、これにStringComparerを指定することで、文字列比較の際の動作を指定できます。

Sort + IComparerによって大文字・小文字の違いを考慮したソートを行う
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var list = new List<string>(new string[] {
      "ab", "ABC", "AA", "a", "aa", "abc", "A",
    });

    // 大文字・小文字の違いを無視してソート
    list.Sort(StringComparer.OrdinalIgnoreCase);

    Console.WriteLine(string.Join(", ", list));

    // 大文字・小文字の違いを意識してソート
    list.Sort(StringComparer.Ordinal);

    Console.WriteLine(string.Join(", ", list));
  }
}
OrderBy + IComparerによって大文字・小文字の違いを考慮したソートを行う
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    var list = new List<string>(new string[] {
      "ab", "ABC", "AA", "a", "aa", "abc", "A",
    });

    // 大文字・小文字の違いを無視してソート
    var ord1 = list.OrderBy(s => s, StringComparer.OrdinalIgnoreCase);

    Console.WriteLine(string.Join(", ", ord1));

    // 大文字・小文字の違いを意識してソート
    var ord2 = list.OrderBy(s => s, StringComparer.Ordinal);

    Console.WriteLine(string.Join(", ", ord2));
  }
}
Sort + IComparerによって大文字・小文字の違いを考慮したソートを行う
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim list As New List(Of String)(New String() { _
      "ab", "ABC", "AA", "a", "aa", "abc", "A" _
    })

    ' 大文字・小文字の違いを無視してソート
    list.Sort(StringComparer.OrdinalIgnoreCase)

    Console.WriteLine(String.Join(", ", list))

    ' 大文字・小文字の違いを意識してソート
    list.Sort(StringComparer.Ordinal)

    Console.WriteLine(string.Join(", ", list))
  End Sub
End Class
OrderBy + IComparerによって大文字・小文字の違いを考慮したソートを行う
Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim list As New List(Of String)(New String() { _
      "ab", "ABC", "AA", "a", "aa", "abc", "A" _
    })

    ' 大文字・小文字の違いを無視してソート
    For Each str As String In list.OrderBy(Function(s) s, StringComparer.OrdinalIgnoreCase)
      Console.Write("{0}, ", str)
    Next
    Console.WriteLine()

    ' 大文字・小文字の違いを意識してソート
    For Each str As String In list.OrderBy(Function(s) s, StringComparer.Ordinal)
      Console.Write("{0}, ", str)
    Next
    Console.WriteLine()
  End Sub
End Class
実行結果
a, A, AA, aa, ab, ABC, abc
A, AA, ABC, a, aa, ab, abc

なお、IComparerについては大小関係の定義と比較、StringComparerについて文字列と比較オプション・カルチャの並べ替え規則で詳しく解説しています。

日付型

DateTimeとDateTimeOffsetは日付・時間の古い順に並べ替えられます。

並べ替えに際して、DateTimeではKindプロパティの値は無視されて日時のみが比較されます。 したがって、2000-01-01T00:00:00+00:00(UTC)と2000-01-01T00:00:00+09:00(ローカル時刻)と2000-01-01T00:00:00(タイムゾーン指定なし)はいずれも同一の値として扱われます。 DateTimeの並べ替えでは、自動的にUTCに変換されてから並べ替えられるといったことはありません。 そのため、ToLocalTime・ToUniversalTimeメソッドを使って現地時刻・UTCのいずれかに統一してからソートしないと意図しない結果になる場合があります。

一方DateTimeOffsetでは、日時に加えてOffsetプロパティの値が考慮されて比較されます。 したがって、2000-01-01T00:00:00+09:002000-01-01T00:00:00+00:002000-01-01T00:00:00-05:00はいずれも異なる値として比較されます。

実行結果
DateTime
2000-01-01T00:00:00.0000000Z        (2000-01-01T00:00:00.0000000Z)
2000-01-01T00:00:00.0000000+09:00   (1999-12-31T15:00:00.0000000Z)
2000-01-01T06:00:00.0000000Z        (2000-01-01T06:00:00.0000000Z)
2000-01-01T18:00:00.0000000Z        (2000-01-01T18:00:00.0000000Z)
2000-01-02T00:00:00.0000000Z        (2000-01-02T00:00:00.0000000Z)
2000-01-02T00:00:00.0000000+09:00   (2000-01-01T15:00:00.0000000Z)
DateTimeOffset
2000-01-01T00:00:00.0000000+09:00   (1999-12-31T15:00:00.0000000+00:00)
2000-01-01T00:00:00.0000000+00:00   (2000-01-01T00:00:00.0000000+00:00)
2000-01-01T00:00:00.0000000-05:00   (2000-01-01T05:00:00.0000000+00:00)
2000-01-02T00:00:00.0000000+09:00   (2000-01-01T15:00:00.0000000+00:00)
2000-01-02T00:00:00.0000000+00:00   (2000-01-02T00:00:00.0000000+00:00)
2000-01-02T00:00:00.0000000-05:00   (2000-01-02T05:00:00.0000000+00:00)
日付型の値のソート順序
using System;

class Sample {
  static void Main()
  {
    var dateTimeArray = new DateTime[] {
      new DateTime(2000, 1, 1,  0, 0, 0, DateTimeKind.Local),
      new DateTime(2000, 1, 1,  0, 0, 0, DateTimeKind.Utc),
      new DateTime(2000, 1, 1,  6, 0, 0, DateTimeKind.Utc),
      new DateTime(2000, 1, 1, 18, 0, 0, DateTimeKind.Utc),
      new DateTime(2000, 1, 2,  0, 0, 0, DateTimeKind.Local),
      new DateTime(2000, 1, 2,  0, 0, 0, DateTimeKind.Utc),
    };
    var dateTimeOffsetArray = new DateTimeOffset[] {
      new DateTimeOffset(2000, 1, 1, 0, 0, 0, new TimeSpan( 0, 0, 0)),
      new DateTimeOffset(2000, 1, 1, 0, 0, 0, new TimeSpan(-5, 0, 0)),
      new DateTimeOffset(2000, 1, 1, 0, 0, 0, new TimeSpan(+9, 0, 0)),
      new DateTimeOffset(2000, 1, 2, 0, 0, 0, new TimeSpan( 0, 0, 0)),
      new DateTimeOffset(2000, 1, 2, 0, 0, 0, new TimeSpan(-5, 0, 0)),
      new DateTimeOffset(2000, 1, 2, 0, 0, 0, new TimeSpan(+9, 0, 0)),
    };

    Array.Sort(dateTimeArray);
    Array.Sort(dateTimeOffsetArray);

    Console.WriteLine("DateTime");
    foreach (var val in dateTimeArray) {
      Console.WriteLine("{0,-35:o} ({1:o})", val, val.ToUniversalTime());
    }

    Console.WriteLine("DateTimeOffset");
    foreach (var val in dateTimeOffsetArray) {
      Console.WriteLine("{0,-35:o} ({1:o})", val, val.ToUniversalTime());
    }
  }
}
日付型の値のソート順序
Imports System

Class Sample
  Shared Sub Main()
    Dim dateTimeArray As DateTime() = New DateTime() { _
      New DateTime(2000, 1, 1,  0, 0, 0, DateTimeKind.Local), _
      New DateTime(2000, 1, 1,  0, 0, 0, DateTimeKind.Utc), _
      New DateTime(2000, 1, 1,  6, 0, 0, DateTimeKind.Utc), _
      New DateTime(2000, 1, 1, 18, 0, 0, DateTimeKind.Utc), _
      New DateTime(2000, 1, 2,  0, 0, 0, DateTimeKind.Local), _
      New DateTime(2000, 1, 2,  0, 0, 0, DateTimeKind.Utc) _
    }
    Dim dateTimeOffsetArray As DateTimeOffset() = New DateTimeOffset() { _
      New DateTimeOffset(2000, 1, 1, 0, 0, 0, new TimeSpan( 0, 0, 0)), _
      New DateTimeOffset(2000, 1, 1, 0, 0, 0, new TimeSpan(-5, 0, 0)), _
      New DateTimeOffset(2000, 1, 1, 0, 0, 0, new TimeSpan(+9, 0, 0)), _
      New DateTimeOffset(2000, 1, 2, 0, 0, 0, new TimeSpan( 0, 0, 0)), _
      New DateTimeOffset(2000, 1, 2, 0, 0, 0, new TimeSpan(-5, 0, 0)), _
      new DateTimeOffset(2000, 1, 2, 0, 0, 0, new TimeSpan(+9, 0, 0)) _
    }

    Array.Sort(dateTimeArray)
    Array.Sort(dateTimeOffsetArray)

    Console.WriteLine("DateTime")
    For Each val As DateTime In dateTimeArray
      Console.WriteLine("{0,-35:o} ({1:o})", val, val.ToUniversalTime())
    Next

    Console.WriteLine("DateTimeOffset")
    For Each val As DateTimeOffset In dateTimeOffsetArray
      Console.WriteLine("{0,-35:o} ({1:o})", val, val.ToUniversalTime())
    Next
  End Sub
End Class

日付・時間の新しい順に並べ替えるには、次の例のように並べ替え順序を通常と逆の順序に定義したメソッドを用意してからソートします。 逆順(降順)でのソート方法については基本型のソートと昇順・降順でのソート §.降順でのソートを参照してください。

日付型を日付・時間の新しい順でソートする
using System;

class Sample {
  // DateTimeを通常と逆の順に並べ替えるためのメソッド
  static int CompareDateTimeReverseOrder(DateTime x, DateTime y)
  {
    // 逆順で比較
    return DateTime.Compare(y, x);
    // 次のようにしても同じ
    //return -DateTime.Compare(x, y);
    //return y.CompareTo(x);
  }

  static void Main()
  {
    var dateTimeArray = new DateTime[] {
      new DateTime(2000, 1, 1,  0, 0, 0),
      new DateTime(2000, 1, 1, 12, 0, 0),
      new DateTime(2000, 1, 2,  0, 0, 0),
      new DateTime(2000, 2, 1,  0, 0, 0),
      new DateTime(2001, 1, 1,  0, 0, 0),
    };

    // デフォルトの順にソート
    Array.Sort(dateTimeArray);

    Console.WriteLine("default order");
    foreach (var val in dateTimeArray) {
      Console.WriteLine("{0:f}", val);
    }

    // 逆順にソート
    Array.Sort(dateTimeArray, CompareDateTimeReverseOrder);

    Console.WriteLine("reverse order");
    foreach (var val in dateTimeArray) {
      Console.WriteLine("{0:f}", val);
    }
  }
}
日付型を日付・時間の新しい順でソートする
Imports System

Class Sample
  ' DateTimeを通常と逆の順に並べ替えるためのメソッド
  Shared Function CompareDateTimeReverseOrder(ByVal x As DateTime, ByVal y As DateTime) As Integer
    ' 逆順で比較
    Return DateTime.Compare(y, x)
    ' 次のようにしても同じ
    'Return -DateTime.Compare(x, y)
    'Return y.CompareTo(x)
  End Function

  Shared Sub Main()
    Dim dateTimeArray As DateTime() = New DateTime() { _
      New DateTime(2000, 1, 1,  0, 0, 0), _
      New DateTime(2000, 1, 1, 12, 0, 0), _
      New DateTime(2000, 1, 2,  0, 0, 0), _
      New DateTime(2000, 2, 1,  0, 0, 0), _
      New DateTime(2001, 1, 1,  0, 0, 0) _
    }

    ' デフォルトの順にソート
    Array.Sort(dateTimeArray)

    Console.WriteLine("default order")
    For Each val As DateTime In dateTimeArray
      Console.WriteLine("{0:f}", val)
    Next

    ' 逆順にソート
    Array.Sort(dateTimeArray, AddressOf CompareDateTimeReverseOrder)

    Console.WriteLine("reverse order")
    For Each val As DateTime In dateTimeArray
      Console.WriteLine("{0:f}", val)
    Next
  End Sub
End Class
実行結果
default order
2000年1月1日 0:00
2000年1月1日 12:00
2000年1月2日 0:00
2000年2月1日 0:00
2001年1月1日 0:00
reverse order
2001年1月1日 0:00
2000年2月1日 0:00
2000年1月2日 0:00
2000年1月1日 12:00
2000年1月1日 0:00

列挙体

列挙体は、整数型に準じた並べ替えが行われ、各メンバに与えられている値の大きさに従って小さい順に並べ替えられます。

実行結果
MinusTwo (-2)
MinusOne (-1)
Zero     (0)
One      (1)
Two      (2)
列挙体の値のソート順序
using System;

enum Number {
  MinusTwo = -2,
  MinusOne = -1,
  Zero = 0,
  One = 1,
  Two = 2,
}

class Sample {
  static void Main()
  {
    var arr = new Number[] {
      Number.Zero, Number.One, Number.MinusOne, Number.Two, Number.MinusTwo,
    };

    Array.Sort(arr);

    foreach (var val in arr) {
      Console.WriteLine("{0,-8:G} ({0:D})", val);
    }
  }
}
列挙体の値のソート順序
Imports System

Enum Number
  MinusTwo = -2
  MinusOne = -1
  Zero = 0
  One = 1
  Two = 2
End Enum

Class Sample
  Shared Sub Main()
    Dim arr As Number() = New Number() { _
      Number.Zero, Number.One, Number.MinusOne, Number.Two, Number.MinusTwo _
    }

    Array.Sort(arr)

    For Each val As Number In arr
      Console.WriteLine("{0,-8:G} ({0:D})", val)
    Next
  End Sub
End Class

列挙体をメンバ名の順(値に割り当てられている名前の順)に並べるには、次のように並べ替え順序を定義する必要があります。 ただ、次の例にあるように、列挙体ではメンバ名で定義されていない値も設定できるため、そういった値をどう取り扱うかについての考慮も必要となってきます。

列挙体の値をメンバ名の順にソートする
using System;

enum Number {
  MinusTwo = -2,
  MinusOne = -1,
  Zero = 0,
  One = 1,
  Two = 2,
}

class Sample {
  // 列挙体の値を比較するメソッド
  static int CompareNumber(Number x, Number y)
  {
    // 列挙体の値を文字列(メンバ名)に変換
    var xx = x.ToString("G");
    var yy = y.ToString("G");

    // 文字列として比較した結果を返す
    return string.Compare(xx, yy);
  }

  static void Main()
  {
    var arr = new Number[] {
      Number.Zero, Number.One, Number.MinusOne, Number.Two, Number.MinusTwo,
      (Number)3, // <- 列挙体のメンバで定義されていない値
    };

    Array.Sort(arr, CompareNumber);

    foreach (var val in arr) {
      Console.WriteLine("{0,-8:G} ({0:D})", val);
    }
  }
}
列挙体の値をメンバ名の順にソートする
Imports System

Enum Number
  MinusTwo = -2
  MinusOne = -1
  Zero = 0
  One = 1
  Two = 2
End Enum

Class Sample
  ' 列挙体の値を比較するメソッド
  Shared Function CompareNumber(ByVal x As Number, ByVal y As Number) As Integer
    ' 列挙体の値を文字列(メンバ名)に変換
    Dim xx As String = x.ToString("G")
    Dim yy As String = y.ToString("G")

    ' 文字列として比較した結果を返す
    Return String.Compare(xx, yy)
  End Function

  Shared Sub Main()
    Dim arr As Number() = New Number() { _
      Number.Zero, Number.One, Number.MinusOne, Number.Two, Number.MinusTwo, _
      CType(3, Number) _
    }

    Array.Sort(arr, AddressOf CompareNumber)

    For Each val As Number In arr
      Console.WriteLine("{0,-8:G} ({0:D})", val)
    Next
  End Sub
End Class
実行結果
3        (3)
MinusOne (-1)
MinusTwo (-2)
One      (1)
Two      (2)
Zero     (0)

列挙体の書式・文字列化については列挙体の基本と操作書式指定子を参照してください。