佐々木屋

技術的なことから趣味まで色々書きます

静的クラスとシングルトン

インスタンスを複数作成しないで、どのクラスからも同じインスタンスを見に行くといった場合は静的クラスを利用します。VB.NETの場合はモジュールがほぼ同じような役割(全く一緒ではない)です。

public static class StaticClass {
    public static string UserName { get; set; }
    public static string UserID { get; set; }
}
Public Module StaticClass
    Public Property UserName As String
    Public Property UserID As String
End Module



静的クラスの場合は継承が使用できないことや、インスタンスを特定場面で入れ替えたい場合に対応できない問題があります。そこでシングルトンが必要になります。

シングルトンはクラスのインスタンスが一つであることを保証する設計です。基本的には、外部ファイルのような特定リソースへの場所を一つにしたい場合や、固定値を定義する場合などに使用します。

シングルトン設計は以下の要領で行います。

  1. コンストラクタをprivateにしてインスタンス作成を禁止する
  2. インスタンスをプライベートな静的メンバ変数に保持する
  3. インスタンスの生成を静的コンストラクタまたは静的メンバ変数の初期化で行う
  4. インスタンスを公開する静的なメソッドかプロパティを追加する
public class SingletonClass : Class1 {
    private static SingletonClass obj = new SingletonClass();
        
    private SingletonClass() { }

    public static SingletonClass Instance {
        get {
            return obj;
        }
    }

    public string UserName { get; set; }
    public string UserID { get; set; }
}
Public Class SingletonClass
    Inherits Class1

    Private Shared obj As New SingletonClass()

    Private Sub New()
    End Sub

    Public Shared ReadOnly Property Instance() As SingletonClass
        Get
            Return obj
        End Get
    End Property

    Public Property UserName As String
    Public Property UserID As String
End Class

コンストラクタをprivateにすることで、クラス外からのインスタンス化を禁止しています。唯一のインスタンスはクラスが初めて使用される時にインスタンス保持メンバー変数宣言時に生成されます。インスタンスの受け渡しは静的プロパティを介して行われます。これは静的メソッドで実装しても構いません。

別の手法として、インスタンス生成を静的コンストラクタで行うことも可能です。インスタンスの受け渡しも静的メソッドで実現してみましょう。

public class SingletonClass : Class1 {
    private static SingletonClass obj;

    private SingletonClass() { }
    static SingletonClass() {
        obj = new SingletonClass();
    }

    public static SingletonClass Instance() {
        return obj;
    }

    public string UserName { get; set; }
    public string UserID { get; set; }
}
Public Class SingletonClass
    Inherits Class1

    Private Shared obj As SingletonClass

    Private Sub New()
    End Sub
    Shared Sub New()
        obj = New SingletonClass()
    End Sub

    Public Shared Function Instance() As SingletonClass
        Return obj
    End Function

    Public Property UserName As String
    Public Property UserID As String
End Class



なお、C#VB.NET)は.NET Frameworkによって静的な初期化が確実に実行されますので、JavaC++のような動的な初期化を行う必要はありません。なお、シングルトンはスレッドセーフです。

また、.NET Framework上では、シングルトンのインスタンス破棄はアプリケーション終了時に自動的に行われます。破棄すべきリソースを保持しており、明示的な破棄が必要な場合は、通常クラス同様にIDisposableインターフェースを実装すれば良いことになります。

ここがダメだよ!VB.NET⑤(静的クラスが無いのよ)

そうなんです。VB.NETには静的クラスが無いんです。

「モジュールがあるやん?」

と思った方。
VB.NETのモジュールがC#の静的クラスとよく混同されますが、残念ながら100%同じというわけではないのです。

呼び出し方

静的クラスの場合、メンバーの呼び出しは同一名前空間にあってもクラス名が必要です。

public static class TestStaticClass {
    public static void StaticMethod() {
        Console.WriteLine("hoge");
    }
}

public static void Main() {
    TestStaticClass.StaticMethod();
}

この為、名前衝突が起こりにくいですし、明示的にどこから呼ばれているのかも分かります。


モジュールの場合、メンバーの呼び出しはクラス名をそのまま呼び出せてしまいます。

Public Module TestModule
    Public Sub StaticMethod()
        Console.WriteLine("hoge")
    End Sub
End Module

Public Sub Main()
    StaticMethod()
End Sub

これは結構致命的で、どこのクラスから呼び出されたものなのかも不明です。また名前衝突も発生しやすくなります。

初期化タイミング

少し分かりにくい&影響が見えない部分ですが、静的クラスとモジュールではbeforefieldinit フラグの設定有無により初期化タイミングが異なります。

静的クラスの場合、通常beforefieldinitフラグが設定されますので、静的クラスへアクセスする前に初期化されることが保証されています。その為、プログラム実行中は静的クラスが初期化されたかどうかのチェックは内部的に実行されることはありません。

モジュールの場合、beforefieldinitフラグは設定されていませんので、モジュールへアクセスする時に初めて初期化されます。よって、プログラム実行中にモジュールが初期化されたかどうかのチェックが行われます。この処理は場合によってはアプリケーション自体のパフォーマンスに影響を及ぼします。

デザインパターン

以下に一つでも当てはまる方は、プログラミング設計を少し勉強した方が良いかもしれません。

・最初は問題ないが、だんだん読みづらいコードにある
・スパゲティコードになってしまう
オブジェクト指向の恩恵を生かしきれていない
・コメントが無いとさっぱり分からない

デザインパターン

「事例」みたいなものなのですが、オブジェクト指向型開発において、現場でよく使われる設計を先人たちがパターン化してくれたものが「デザインパターン」です。

発端はGoFGang of Four)と呼ばれるエーリヒ・ガンマ、リチャード・ヘルム、ラルフ・ジョンソン、ジョン・ブリシディースの4人のことで、1995年に発刊された「オブジェクト指向における再利用のためのデザインパターン」で一躍世に広まることになります。

C言語にクラス概念が追加されたC++と、Java、最後にC#と、これらオブジェクト指向の代表言語でよく使われる設計を網羅したものとなります。

デザインパターンを意識することで、プログラムがオブジェクト指向の概念に沿った、「再利用」・「拡張性」・「独立性」が自然と設計されるようになります。

メリット

過去の偉大な先人たちの失敗から洗練された設計なので、当然オブジェクト指向の恩恵を受けることが出来ることは言うまでもありません。

それ以外に、

  • 設計の変更に強い
  • 不具合に強い(発見しやすい)
  • 汎用性が高い

などが挙げられます。

この後、少しずつですが例をとって紹介していければと思っています。

LINQ同様、デザインパターンは1冊の書籍で成り立つほどの分量です。多種多様な言語のものが多く出回っていますので、一度手にとってみてはいかがでしょうか。

但し、当然ですがオブジェクト指向の概念を100%理解した状態でなければ正直意味がありませんので、怪しい場合はよく復習しておいて下さい。

GroupByメソッド③

GroupByメソッドはクラスコレクション以外、通常のarrayコレクションでも使用可能です。

List<string[]> lst = new List<string[]> {
                    new string[]{ "1", "25", "5", "3000"},
                    new string[]{ "1", "31", "10", "4000"},
                    new string[]{ "2", "31", "98", "12800"},
                    new string[]{ "1", "25", "11", "250"}
};

var res = lst.GroupBy(x =>Tuple.Create(x[0], x[1] ));
Dim lst As New List(Of String()) From {
                    New String() {"1", "25", "5", "3000"},
                    New String() {"1", "31", "10", "4000"},
                    New String() {"2", "31", "98", "12800"},
                    New String() {"1", "25", "11", "250"}}

Dim res = lst.GroupBy(Function(x) Tuple.Create(x(0), x(1)))



GroupByメソッドにより、集約したコレクションの結果を使って集計することが簡単に出来ます。

var res = lst.GroupBy((x) => Tuple.Create(x[0],x[1]))
                                  .Select((y) => new {
                                      KaisyaCD = y.Key.Item1,
                                      SyainCD = y.Key.Item2,
                                      SumValue = y.Sum((s) => int.Parse(s[2]))
                                  }
                                  );

foreach r In res
    Console.WriteLine(r.KaisyaCD + ":" + r.SyainCD + " " + r.SumValue);
Next
Dim res = lst .GroupBy(Function(s) Tuple.Create(s(0), s(1))).Select(
                    Function(y) New With {
                            .KaisyaCD = y.Key.Item1,
                            .SyainCD = y.Key.Item2,
                            .SumValue = y.Sum(Function(s) Integer.Parse(s(2)))
                        })

For Each r In res
    Console.WriteLine(r.KaisyaCD & ":" & r.SyainCD & " " & r.SumValue)
Next
1:25 16
1:31 10
2:31 98

GroupByメソッド②

GroupByメソッドには複合キーを指定することも可能です。複合キーの指定の仕方は2通りあり、new(VB.NETはNew With)による匿名型で指定する方法とTuple.Createで指定する方法があります。

List<Uriage> lst = new List<Uriage>() {
                    new Uriage{KaisyaCD="1",SyainCD = "25",UriageSu = 5, UriageKingaku=3000},
                    new Uriage{KaisyaCD="1",SyainCD = "31",UriageSu = 10, UriageKingaku=4000},
                    new Uriage{KaisyaCD="2",SyainCD = "31",UriageSu = 98, UriageKingaku=12800},
                    new Uriage{KaisyaCD="1",SyainCD = "25",UriageSu = 11, UriageKingaku=250}
                };

//new
var res = lst.GroupBy(x => new { x.KaisyaCD, x.SyainCD });

//Tuple.Create
var res = lst.GroupBy(x => Tuple.Create(x.KaisyaCD, x.SyainCD));

foreach (var r1 in res) {
    Console.WriteLine(r1.Key);
    foreach (var r2 in r1) {
        Console.WriteLine(r2.UriageSu);
    }
}

VB.NETのNew Withの場合は、匿名型に対してKeyキーワードをつけないと集計されません。

Dim lst As New List(Of Uriage) From {
                    New Uriage With {.KaisyaCD = "1", .SyainCD = "25", .UriageSu = 5, .UriageKingaku = 3000},
                    New Uriage With {.KaisyaCD = "1", .SyainCD = "31", .UriageSu = 10, .UriageKingaku = 4000},
                    New Uriage With {.KaisyaCD = "2", .SyainCD = "31", .UriageSu = 98, .UriageKingaku = 12800},
                    New Uriage With {.KaisyaCD = "1", .SyainCD = "25", .UriageSu = 11, .UriageKingaku = 250}
                }

'New With
Dim res = lst.GroupBy(Function(x) New With {Key x.KaisyaCD, Key x.SyainCD})

'Tuple.Create
Dim res = lst.GroupBy(Function(x) Tuple.Create(x.KaisyaCD, x.SyainCD))

For Each r1 In res
    Console.WriteLine(r1.Key)
    For Each r2 In r1
        Console.WriteLine(r2.UriageSu)
    Next
Next
{ KaisyaCD = 1, SyainCD = 25 }
5
11
{ KaisyaCD = 1, SyainCD = 31 }
10
{ KaisyaCD = 2, SyainCD = 31 }
98

GroupByメソッド①

GroupByメソッドはLINQの中では少し特殊(私だけか?)で、単純なlistやarrayのようなコレクションでは利用しません。コレクションコレクションの中にコレクションが格納されているような場合の集計に非常に便利な機能となります。複雑な集計を簡単に書くことができるのです。

データテーブルのような階層構造を持ったコレクションの小計・合計を求めたりも簡単に出来ます。

以下のような状態を保持するようなクラスと、このクラスを要素にもつようなコレクションがあります。一種のテーブルのような構造です。

public class Uriage {
    public string KaisyaCD { get; set; }
    public string SyainCD { get; set; }
    public int UriageSu { get; set; }
    public int UriageKingaku { get; set; }
}
Public Class Uriage
    Public Property KaisyaCD() As String
    Public Property SyainCD() As String
    Public Property UriageSu() As Integer
    Public Property UriageKingaku() As Integer
End Class

社員番号(SyainCD)で集計します。GroupByメソッドは引数にラムダ式でグループ分けのKeyを指定します。

List<Uriage> lst = new List<Uriage>() {
            new Uriage{KaisyaCD="1",SyainCD = "25",Su = 5, Kingaku=3000},
            new Uriage{KaisyaCD="1",SyainCD = "31",Su = 10, Kingaku=4000},
            new Uriage{KaisyaCD="1",SyainCD = "31",Su = 98, Kingaku=12800},
            new Uriage{KaisyaCD="1",SyainCD = "25",Su  = 11, Kingaku=250}};

var res = lst.GroupBy(x => x.SyainCD);
foreach(var r in res) {
    Console.WriteLine(r.Key);
}
Dim lst As New List(Of Uriage) From {
            New Uriage With {.KaisyaCD = "1", .SyainCD = "25", .Su  = 5, .Kingaku = 3000},
            New Uriage With {.KaisyaCD = "1", .SyainCD = "31", .Su  = 10, .Kingaku= 4000},
            New Uriage With {.KaisyaCD = "1", .SyainCD = "31", .Su  = 98, .Kingaku= 12800},
            New Uriage With {.KaisyaCD = "1", .SyainCD = "25", .Su  = 11, .Kingaku= 250}}
Dim res = lst.GroupBy(Function(x) x.SyainCD)
For Each r In res
    Console.WriteLine(r.Key)
Next
25
31


このように集計後はIEnumerableで列挙できるような状態になります。

SelectManyメソッド

単純な配列に対して処理する場合は、SELECTメソッドで処理できますが、配列に配列を持つようなコレクションは少し面倒です。

例えば、

List<string[]> values = new List<string[]> 
                                {new string[] { "京都府", "京都市"},
                                 new string[] { "奈良県", "奈良市"},
                                 new string[] { "大阪府", "大阪市"},
                                 new string[] { "兵庫県", "神戸市"},
                                 new string[] { "和歌山", "和歌山市"}};
Dim values As New List(Of String()) From 
                                   {New String() {"京都府", "京都市"},
                                   New String() {"奈良県", "奈良市"},
                                   New String() {"大阪府", "大阪市"},
                                   New String() {"兵庫県", "神戸市"},
                                   New String() {"和歌山", "和歌山市"}}

このようなコレクションです。

これを一つのコレクションにするには、SelectメソッドでString.Joinメソッドなどを複合的に利用して処理する必要があります。

var res = values.Select(x => String.Join(",", x)).ToArray();
Console.WriteLine(String.Join(",", res));
Dim res = values.Select(Function(x) String.Join(",", x)).ToArray()
Console.WriteLine(String.Join(",", res))
京都府,京都市,奈良県,奈良市,大阪府,大阪市,兵庫県,神戸市,和歌山,和歌山市



これはSelectManyメソッドを使用すると簡単に一つのコレクションへ変形できます。この処理を「平坦化」と呼びます。

var res = values.SelectMany(x => x).ToArray();
Console.WriteLine(String.Join(",", res));
Dim res = values.SelectMany(Function(x) x).ToArray()
Console.WriteLine(String.Join(",", res))
京都府,京都市,奈良県,奈良市,大阪府,大阪市,兵庫県,神戸市,和歌山,和歌山市