静的クラスとシングルトン
インスタンスを複数作成しないで、どのクラスからも同じインスタンスを見に行くといった場合は静的クラスを利用します。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
静的クラスの場合は継承が使用できないことや、インスタンスを特定場面で入れ替えたい場合に対応できない問題があります。そこでシングルトンが必要になります。
シングルトンはクラスのインスタンスが一つであることを保証する設計です。基本的には、外部ファイルのような特定リソースへの場所を一つにしたい場合や、固定値を定義する場合などに使用します。
シングルトン設計は以下の要領で行います。
- コンストラクタをprivateにしてインスタンス作成を禁止する
- インスタンスをプライベートな静的メンバ変数に保持する
- インスタンスの生成を静的コンストラクタまたは静的メンバ変数の初期化で行う
- インスタンスを公開する静的なメソッドかプロパティを追加する
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によって静的な初期化が確実に実行されますので、JavaやC++のような動的な初期化を行う必要はありません。なお、シングルトンはスレッドセーフです。
また、.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フラグは設定されていませんので、モジュールへアクセスする時に初めて初期化されます。よって、プログラム実行中にモジュールが初期化されたかどうかのチェックが行われます。この処理は場合によってはアプリケーション自体のパフォーマンスに影響を及ぼします。
デザインパターン
以下に一つでも当てはまる方は、プログラミング設計を少し勉強した方が良いかもしれません。
・最初は問題ないが、だんだん読みづらいコードにある
・スパゲティコードになってしまう
・オブジェクト指向の恩恵を生かしきれていない
・コメントが無いとさっぱり分からない
デザインパターン
「事例」みたいなものなのですが、オブジェクト指向型開発において、現場でよく使われる設計を先人たちがパターン化してくれたものが「デザインパターン」です。
発端はGoF(Gang of Four)と呼ばれるエーリヒ・ガンマ、リチャード・ヘルム、ラルフ・ジョンソン、ジョン・ブリシディースの4人のことで、1995年に発刊された「オブジェクト指向における再利用のためのデザインパターン」で一躍世に広まることになります。
C言語にクラス概念が追加されたC++と、Java、最後にC#と、これらオブジェクト指向の代表言語でよく使われる設計を網羅したものとなります。
デザインパターンを意識することで、プログラムがオブジェクト指向の概念に沿った、「再利用」・「拡張性」・「独立性」が自然と設計されるようになります。
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))
京都府,京都市,奈良県,奈良市,大阪府,大阪市,兵庫県,神戸市,和歌山,和歌山市