Hatena::ブログ(Diary)

Architect Life このページをアンテナに追加 RSSフィード

2012-08-05

Readable Code その1

いいコード書いてますか?

最近読んだ「Readable Code」という本が面白かったので、その感想とためになったことを紹介したいと思います。

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

この本ではいいコードを読み易いコード定義付けて、そういったコードを書くためにはどうすればいいかという事を具体的なコード例を交えて解説しています。

コードのインデントや書式などの見た目を整えるといった事から、変数の命名や意味付け、関数構造化、デザインパターンの導入まで、その内容は多岐に渡ります。今回はその中でも特に使えそうな内容について説明したいと思います。

と、その前に個人的にいいコードの条件として考えているポイントについて説明しておきます。

俺的いいコードの条件

いいコードの条件としては、まずバグが無いというのが理想でしょう。

しかしプログラムは書いた通りに完璧に動作しますが、それを実装する人間は完璧とは程遠い不完全な代物です。バグの無いプログラムを書く事は不可能と言えます。

では、いいコードの条件としてバグが無いというのを挙げるのは非現実なことでしょうか?

確かにそうかもしれません、しかしこう考える事もできます。

バグを無くす事は出来ないがバグが発生した時、原因の発見修正の迅速化修正による影響範囲を局所化する事はできる。

間違いは必ず起こることを認めて、それをいかにして迅速に確実に収束させるかを目指す。

これがエンジニアとして目指すべき姿勢だと思いませんか?そのためにできる事ならなんだってする。それが在るべきエンジニア像でしょう。


少し話がそれますが、プログラミングはどこまで行っても結局のところ職人技です。そこにはどうしても属人性が含まれてしまいます。

しかし、チーム・組織で働く以上、属人性はなるべく入り込まない方が不確定要素が減り、色々とコントロールし易くなります。

実際私もフレームワークドキュメントを作る時は、誰でも使えるように、誰でも理解出来るように注意しています。

これはチーム・企業で働く以上、当たり前の行動規範でしょう。

しかし、いちエンジニア・いちプログラマとして見た場合、こんなことはクソくらえとなります。

属人性が排除されたコードなんてものは、魂のこもらない人形みたいなもの、ようするにクソだということです。

こんなことは公には言えないですが、いち企業人といちエンジニアとで思想や信条を使い分ける必要はあるが、エンジニアとしての魂や誇りといったものは捨てる必要はなく、持ち続けて欲しい、持ち続けたいと思う今日この頃です。


閑話休題

では、ここから本書に書かれている内容に触れていきます。

目次

Readable Codeの目次を以下に示します。

1.表面上の改善

  • 名前情報を詰め込む
  • 誤解されない名前
  • 美しさ
  • コメントすべきことを知る
  • コメントは正確で簡潔に

2.ループロジックの単純化

  • 制御フローを読みやすくする
  • 巨大な式を分割する
  • 変数と読みやすさ

3.コードの再構成

  • 無関係の下位問題を抽出する
  • 一度に1つのことを
  • コードに思いを込める
  • 短いコードを書く

4.選抜テーマ

目次を見ただけでどういった内容の事が書かれているかだいたい把握できるでしょう。

前置きが長くなってしまったので、本編はまた次回に続きます。

つづく…

2011-05-16

アーキテクト宣言

長い間お休みしてましたが、転職して東京に来てから早二年、ようやくアーキテクトとしての道を歩みはじめました。

プログラマーとアーキテクトの違いは、本人がアーキテクトと宣言したかどうかなんて事がよく言われたりしますが、実際の所、周りが認めてくれなければアーキテクトもクソもないわけで。。。

まぁ、そんなこんなで周りに認めてもらってアーキテクトとして働ける環境仕事がやっとこさ構築できたというわけです。

これからは今までと同様に技術的なネタも書いていきますが、それプラスアーキテクチャとは何か?とか、アーキテクチャをどのようにして他者に伝えるか?とか、アーキテクチャに関する勉強ネタなんかも書いていきたいと思います。

それでは、

俺はアーキテクトだ!

2009-12-10

NDesk.Options(Mono) - コマンドラインパーサー

Monoでコマンドラインパーサーがあったので使ってみた。

使い方はこんな感じ

string data = null;
bool help   = false;
int verbose = 0;

var p = new OptionSet () {
  { "file=",      v => data = v },
  { "v|verbose",  v => { ++verbose } },
  { "h|?|help",   v => help = v != null }
};
List<string> extra = p.Parse(args);

OptionSetAddメソッドが定義されていて、以下のようなシグネチャのメソッドが定義されている。

  • Add(string prototype, Action<string> action);
  • Add(string prototype, string description, Action<string> action)

prototype引数には、コマンドラインのオプション引数の引数名を指定する。|(パイプ)はorを意味する。

v|verbose

と指定した場合は、コマンドライン引数として、

PS > コマンド名 -v
PS > コマンド名 -verbose

のように渡すことができる。

末尾に=(イコール)を付けた場合は、

file=

以下のように引数名と値という形で指定できるようになる。

PS > コマンド名 -file=100.txt

description引数はその名の通り、オプション引数の説明になる。これはOptionSetクラスに定義されているWriteOptionDescriptionsメソッドというコマンドラインの説明文を出力するメソッドを呼び出した時にその出力に含まれるようになる。

action引数には、コマンドラインをパースする中で実際にオプション引数が見つかった時にその値をどのように処理するかを委譲するデリゲートを指定する。

これらの設定を行ってから、Parseメソッドを呼び出すと戻り値として、オプション以外のコマンドライン引数がコレクションとして返ってくる。

使い易いといえば使い易いけど、値の設定にデリゲートを渡すという発想が、完全に一般のデベロッパを無視しているので、もう少し使いやすくするために属性ベースで値を取得できるラッパを作ってみた

CommandlineParser

使い方は以下のようになる。

まず、オプション引数を格納するクラスを定義し、値を格納するプロパティCommandlineOption属性でマークする。

MyOptions.cs

class MyOptions {
    [CommandlineOption("n", "name", Description = "名前を指定します。")]
    public string Name { get; set; }
    [CommandlineOption("s", "switch", Description = "スイッチ", OptionType = CommandlineOptionType.Switch)]
    public bool Switch { get; set; }
    [CommandlineOption("value")]
    public int Value { get; set; }
}

後はCommandlineParserクラスをインスタンス化し、Parseメソッドを呼び出すだけ。

[Test]
public void コマンドラインをパースできる() {
    // コマンドライン引数
    var args = new[] { "-n=hoge", "-switch", "value1", "value2", "-value=20" };

    var result = cmdlineParser.Parse<MyOptions>(args);

    Assert.That(result, Is.Not.Null);
    Assert.That(result.Values, Has.Count.EqualTo(2));
    // Valuesプロパティにオプション引数以外の値が入っている。
    Assert.That(result.Values[0], Is.EqualTo("value1"));
    Assert.That(result.Values[1], Is.EqualTo("value2"));

    // オプション引数がカスタム属性でマークしたプロパティに設定されている。
    Assert.That(result.Options.Name, Is.EqualTo("hoge"));
    Assert.That(result.Options.Switch, Is.True);
    Assert.That(result.Options.Value, Is.EqualTo(20));

    // コマンドラインの説明文を取得できる。
    Console.WriteLine(cmdlineParser.GetCommandlineUsage<MyOptions>());
}

戻り値としてCommandlineParseResult<T>が返ってくるので、そこからオプション引数の情報を取得できるようになっている。

探せば色々とあるものですね。

以下ソース

続きを読む

2009-09-24

DataGridViewにデータバインドして編集・削除するのに最適な方法は?

WindowsFormで業務アプリを開発する場合、DataGridViewの様にデータの表示と編集作業を実際のデータソースの種類を問わずに一括してやってくれるコントロールがあると非常に便利である。

単純な編集作業ならば、これにデータバインドしてちょこちょこっとコードを書いてやるだけで、目的仕様を達成できるのだから、ありがたくて涙が出てくる。

.NETにはデータベースアクセスする技術が何種類かあって、どれを選ぶかによってDataGridViewとの連携のし易さもかなり変わってくる。

その辺の違いを調べる為に以下の仕様をいくつかのデータベースアクセス技術で実装して、その違いを比べてみた。

  • 表示するレコードは複数のテーブルをJOINしている(JOINで対応できない場合はビューを使う)。
  • 属性を変更できる。
  • レコードを削除できる。
ER

f:id:coma2n:20090922231201p:image

ためした技術は以下、

  1. DataSet
  2. 型付けDataSet
  3. LINQ to SQL
  4. ORM(iBATIS.NET)
画面

f:id:coma2n:20090924161945p:image

画面ロード時にレコードを全件検索して、画面を閉じる時にレコードに対する変更をコミットする。

DataSet

まずはDataSetから(コードを書くスタイル

MainForm.vb

Imports System.Text
Imports System.Data.SqlClient

Public Class MainForm
    Private Const CONNECTION_STRING = "Data Source=localhost\SQLEXPRESS;Initial Catalog=Sample;Persist Security Info=True;User ID=sa;Password=*******"

    Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
        Dim dataSet = New DataSet()

        Using sqlCon = New SqlConnection() With {.ConnectionString = CONNECTION_STRING}
            sqlCon.Open()

            Dim sql = New StringBuilder()
            sql.AppendLine("SELECT t1.Id,t1.Name,t2.Name AS Organization")
            sql.AppendLine("FROM Person AS t1")
            sql.AppendLine("INNER JOIN Organization AS t2")
            sql.AppendLine("  ON t1.Organization = t2.Id")

            Using sqlDa = New SqlDataAdapter(sql.ToString(), sqlCon)
                sqlDa.Fill(dataSet)
            End Using
        End Using

        DataGridView1.DataSource = dataSet.Tables(0)

        MyBase.OnLoad(e)
    End Sub

    Protected Overrides Sub OnFormClosed(ByVal e As System.Windows.Forms.FormClosedEventArgs)
        Dim dataTable = DirectCast(DataGridView1.DataSource, DataTable).GetChanges()
        ' 変更無し
        If dataTable Is Nothing Then Return

        Using sqlCon = New SqlConnection() With {.ConnectionString = CONNECTION_STRING}
            sqlCon.Open()

            Using sqlDa = New SqlDataAdapter()
                sqlDa.UpdateCommand = New SqlCommand("UPDATE Person SET Name = @Name WHERE Id = @Id", sqlCon)
                sqlDa.UpdateCommand.Parameters.Add("@Name", SqlDbType.VarChar, 50, "Name")
                sqlDa.UpdateCommand.Parameters.Add("@Id", SqlDbType.BigInt, 8, "Id")

                sqlDa.DeleteCommand = New SqlCommand("DELETE Person WHERE Id = @Id", sqlCon)
                sqlDa.DeleteCommand.Parameters.Add( _
                    New SqlParameter("@Id", SqlDbType.BigInt, 8, "Id") With {.SourceVersion = DataRowVersion.Original} _
                )
                sqlDa.Update(dataTable)
            End Using
        End Using

        MyBase.OnFormClosed(e)
    End Sub
End Class

SqlDataAdapterを使い、最低限のコードで済ませているつもり。検索するレコードはJOINするので、SqlCommandBuilderによるUpdate文などの自動生成には対応していない。

なのでUpdateCommandDeleteCommandは独自に設定している。

思っていたよりもコードがすっきりしていて、わかり易い。

型付けDataSet

次、型付けDataSet。こちらはデザイナによるコード生成で全てまかなえるので、コード無し。

SampleDataSet.xsd

f:id:coma2n:20090924161946p:image

GetDataメソッド

f:id:coma2n:20090924161947p:image

UpdatePersonメソッド

f:id:coma2n:20090924161948p:image

DeletePersonメソッド

f:id:coma2n:20090924161949p:image

あとはこいつをデザイナでフォームにぽとぺたして、DataGridViewと関連付けるだけ。

これだけで動く。問題は自分で検索のタイミング更新のタイミングを自由に制御しづらい点。

LINQ to SQL

DLINQ

まずはLINQデザイナでテーブルをぽとぺたする。

SampleDatabase.dbml

f:id:coma2n:20090924163819p:image

画面側

MainForm.vb

Imports System.Data.SqlClient

Public Class MainForm
    Private _db As SampleDatabaseDataContext = New SampleDatabaseDataContext()

    Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
        Dim dataSource = From p In _db.PersonView

        BindingSource1.DataSource = dataSource

        MyBase.OnLoad(e)
    End Sub

    Protected Overrides Sub OnFormClosed(ByVal e As FormClosedEventArgs)
        Dim changeSet = _db.GetChangeSet()
        Dim deleteSet = changeSet.Deletes.Select(Function(o As PersonView) o.Id)
        Dim updateSet = changeSet.Updates.Select( _
            Function(o As PersonView) New With {.Id = o.Id, .Name = o.Name} _
        )
        For Each id In deleteSet
            _db.ExecuteCommand("DELETE Person WHERE Id = {0}", id)
        Next

        For Each o In updateSet
            _db.ExecuteCommand("UPDATE Person SET Name = {0} WHERE Id = {1}", o.Name, o.Id)
        Next

        MyBase.OnFormClosed(e)
    End Sub
End Class

更新するところがLINQでなくなっているのが痛いけど、仕方がない。*1

以外とすっきりしている。しかし、DataSetに比べ若干敷居が高いかもしれない。

ORM(iBATIS.NET)

iBATIS.NETを使った場合

まずはPersonクラス定義する。

Person.vb
Public Class Person

    Private _id As Long
    Public Property Id() As Long
        Get
            Return _id
        End Get
        Set(ByVal value As Long)
            _id = value
        End Set
    End Property

    Private _name As String
    Public Property Name() As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            _name = value
        End Set
    End Property

    Private _organization As String
    Public Property Organization() As String
        Get
            Return _organization
        End Get
        Set(ByVal value As String)
            _organization = value
        End Set
    End Property

End Class

マッピングファイル

Person.xml
<?xml version="1.0" encoding="utf-8" ?>
<sqlMap namespace="OrmSample"
        xmlns="http://ibatis.apache.org/mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <resultMaps>
        <resultMap id="Person" class="OrmSample.Person, OrmSample">
            <result column="Id" property="Id" />
            <result column="Name" property="Name" />
            <result column="Organization" property="Organization" />
        </resultMap>
    </resultMaps>
    
    <statements>
        <select id="selectPerson" resultMap="Person">
            SELECT t1.Id, t1.Name, t2.Name AS Organization
            FROM Person AS t1
            INNER JOIN Organization AS t2
              ON t1.Organization = t2.Id
        </select>
    </statements>
</sqlMap>

画面側

MainForm.vb

Imports System.ComponentModel

Imports IBatisNet.DataMapper
Imports IBatisNet.DataMapper.Configuration

Public Class MainForm
    Private _sqlMapper As ISqlMapper

    Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
        Dim sqlMapBuilder = New DomSqlMapBuilder()
        _sqlMapper = sqlMapBuilder.Configure()

        BindingSource1.DataSource = _sqlMapper.QueryForList(Of Person)("selectPerson", Nothing)

        MyBase.OnLoad(e)
    End Sub

    Protected Overrides Sub OnFormClosed(ByVal e As FormClosedEventArgs)
        ' 面倒くさい

        MyBase.OnFormClosed(e)
    End Sub
End Class

どのオブジェクトが変更されたかを調べるのが面倒くさかったので更新系は実装していない。だいぶ面倒くさいのは確か。

結果をまとめると、

技術参照系更新系総合
(5点満点)
雑感
DataSet4動的なSQLには不向き
型付けDataSet4動的なSQLには不向き。
デザイナを使用するため、画面のコードに直接SQLを書き込む必要がある。
LINQ to SQL3.5イカしてる。
ORM2.5とにかく面倒くさい。
使うならば変更を検知するなんらかの仕組みが必要

参照系ならばどの技術を使っても大差は無い(動的な条件に強いか弱いかぐらい)。

逆に更新系では、変更を検知する仕組みが初めから組み込まれているDataSetやDLINQはかなり強いので、ORMなんかのPONOをエンティティとして使うフレームワークはどうしても弱くなってしまう。

まぁ、DataGridView使って楽したかったらDataSetを使えって事ですね。結論でました。

*1:LINQはビューの更新に対応していない。

2009-09-16

MVPパターンを業務アプリに適用する − 画面遷移

MVPパターンにおける画面遷移のやり方を調べていたが、具体的な情報があまりなく、これだというのが無かったので自分なりに考えてみた。

業務アプリでやるからには極力シンプルで、誰にでもわかるやり方にする必要がある*1

とりあえず考えたのは、下図のようにプレゼンター側で遷移するビューを取り出し、必要な値を設定し表示するというやり方。

概要図

f:id:coma2n:20090916144906p:image:w350

これくらい単純なのが一番いい。

ビューに値を設定する方法としては、

  1. コントロールを参照して直接値を設定する。
  2. プロパティ定義して、間接的に値を設定する。

が考えられるが、プロジェクト的には1.を採用したい。簡単だし、受け入れられ易い。プロパティってなに?って言われちゃうレベルだからね。

しかし、いくつか問題点がある。

  1. コントロールを直接参照するので、コントロール名を変更されたり、削除されたりするとコンパイルエラー
  2. 型の不一致(例えばTextBoxのTextプロパティはString型だけど、欲しいのはInteger型)

1.は、画面がころころ変わるのは事前に設計がちゃんとできていないからなので、それ以前の問題
事前の設計がきちんと行われていれば、コントロール名が変更されたり、削除される事は(ほとんど)無い。

2.は、利用する側でその都度変換をかけていては、あちこちに同じ様なコード記述されてしまうので、
バグの温床になりかねない。

結果として、一番安全なのは、

  1. コントロールはprivateにして、外部からアクセスできないようにする*2
  2. コントロールの値に外部からアクセスする為のプロパティを用意する。
  3. その際、型変換が必要な場合は行う。

3.は、プロパティを定義してもらう為の説得材料にもなりそう。こうしないと危険でしょ?みたいな。

実装

ということで、プレゼンターからビューにアクセスする為の変更を稚拙のMVPフレームワークに施していく。

まずは、PresenterBaseコンテナからビューを取得する為のメソッドを追加する。

PresenterBase.vb(一部省略)
Public MustInherit Class PresenterBase(Of TView As Form)
    Private _container As IUnityContainer

    Public Sub SetContainer(ByVal container As IUnityContainer)
        _container = container
    End Sub

    Protected Function GetView(Of T As Form)() As T
        Return _container.Resolve(Of T)()
    End Function
End Class

コンテナの設定は後でやるとして、GetViewというメソッドを定義した。

このメソッドは単に型パラメータで渡された型のインスタンスResolveメソッドで取り出して返すだけ。

プレゼンターへのコンテナの設定はPresenterExtensionが行う。

PresenterExtension.vb(一部省略)

Public NotInheritable Class PresenterExtension
    Inherits UnityContainerExtension

    Protected Overrides Sub Initialize()
        Me.Context.Strategies.Add( _
            New PresenterInitializeStrategy(Me.Container), UnityBuildStage.Setup _
        )
    End Sub

    Public NotInheritable Class PresenterInitializeStrategy
        Inherits BuilderStrategy

        Private _container As IUnityContainer

        Public Sub New(ByVal container As IUnityContainer)
            _container = container

            Initialize()
        End Sub

        Public Overrides Sub PostBuildUp(ByVal context As IBuilderContext)
            Dim view = context.Existing
            Dim viewType = view.GetType()

            If _presenterDefinitions.ContainsKey(viewType) Then
                Dim presenter = _container.Resolve(_presenterDefinitions(viewType))

                presenter.SetView(view)
                presenter.SetContainer(_container)
            End If

            MyBase.PostBuildUp(context)
        End Sub
    End Class
End Class

見ての通り。

サンプルプログラムには画面遷移が無いので、画面遷移するように下図のメニュー画面を作る。

画面イメージ

f:id:coma2n:20090916170520p:image

コードはこんな感じ

MenuView.vb
Public Class MenuView
    Public Event MenuClick(ByVal sender As Object, ByVal e As MenuClickEventArgs)

    Private Sub menu_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnCalc.Click
        RaiseEvent MenuClick(sender, New MenuClickEventArgs(DirectCast(sender, Button).Tag))
    End Sub
End Class

Public Class MenuClickEventArgs
    Inherits EventArgs

    Private _menuKind As MenuKind

    Public ReadOnly Property MenuKind() As MenuKind
        Get
            Return _menuKind
        End Get
    End Property

    Public Sub New(ByVal menuKind As MenuKind)
        _menuKind = menuKind
    End Sub
End Class

メニューをいくつか定義するつもりなので、プレゼンター側でボタンイベントハンドラを登録させるのはやめて、メニューがクリックされた事を通知する為の専用のイベントを定義した。

クリックされたメニューはMenuClickEventArgsクラスMenuKindプロパティで判断できるようになっている。

MenuKind列挙型の定義は以下

Enums.vb
Public Enum MenuKind
    計算機 = 1
End Enum

で、このメニュー画面のプレゼンター

MenuViewPresenter.vb
Public Class MenuViewPresenter
    Inherits PresenterBase(Of MenuView)

    Protected Overrides Sub OnViewSet(ByVal view As MenuView)
        If view IsNot Nothing Then
            AddHandler view.MenuClick, AddressOf view_MenuClick
        End If

        MyBase.OnViewSet(view)
    End Sub

    Private Sub view_MenuClick(ByVal sender As Object, ByVal e As MenuClickEventArgs)
        Select Case e.MenuKind
            Case MenuKind.計算機
                Dim calcView = GetView(Of CalcView)()
                calcView.StartPosition = FormStartPosition.CenterParent
                calcView.ShowDialog(Me.View)

            Case Else
        End Select
    End Sub
End Class

やっている事はMenuClickイベントにイベントハンドラを登録し、そのイベントハンドラでGetViewメソッドを使ってCalcViewというビュー*3を取り出し表示しているだけ。

実行して「計算機」ボタンをクリックすると、こんな感じ

スクリーンキャプチャ

f:id:coma2n:20090917005939p:image

これで適当な値を入力して計算したりなんかして、計算画面を閉じて、もう一度「計算機」ボタンをクリックすると前の値は保持されるずに初期状態の計算画面が表示される。

GetViewメソッドはDIコンテナから新しいオブジェクトを取り出すだけなので、毎回違うオブジェクトだからこの動きは当然の結果。

でも、使い捨ての画面ならこれでもいいけど、画面の値をずっと保持していたい場合なんかがあった場合、別途画面のインスタンスをどこかに保持しておくか、毎回画面の値をセットし直すなどのなんらかの対処をする必要がある。

こういう事はあまりしたくないので、DIコンテナから毎回同じインスタンスを取得できる、つまりSingletonなビューを取り出せるメソッドを用意しておく。

PresenterBase.vb(一部省略)

Protected Function GetViewOfSingleton(Of T As Form)(ByVal viewName As String) As T
    Dim view = _container.Resolve(Of T)(viewName)
    Dim singletonView = TryCast(view, SingletonViewBase)
    ' オブジェクトのLifetimeをSingletonで管理するオブジェクト
    Dim lifetimeMgr = New ContainerControlledLifetimeManager()

    If singletonView IsNot Nothing Then
        singletonView.SetLifetime(lifetimeMgr)
    End If
    ' Singletonで登録する。
    ' 次回以降、コンテナからSingletonで取り出せるようになる。
    _container.RegisterInstance(viewName, view, lifetimeMgr)

    Return view
End Function

Protected Function GetViewOfSingleton(Of T As Form)(ByVal viewName As String) As T
    Dim view = _container.Resolve(Of T)(viewName)
    Dim lifetimeMgr = _context.GetLifetimeManager(view)

    If lifetimeMgr Is Nothing Then
        lifetimeMgr = New ContainerControlledLifetimeManager()
        ' Singletonで登録する。
        ' 次回以降、コンテナからSingletonで取り出せるようになる。
        _container.RegisterInstance(viewName, view, lifetimeMgr)

        Dim singletonView = TryCast(view, SingletonViewBase)
        ' SingletonViewBaseから継承していれば、Lifetimeの管理を任せる。
        If singletonView IsNot Nothing Then
            singletonView.SetLifetime(lifetimeMgr)
        End If
    End If
    Return view
End Function

GetViewOfSingletonメソッドで取得したビューはSingletonインスタンスになる。もちろんviewName引数で指定した名前単位でだけど。

ついでにこういうのも作っておいた。

SingletonViewBase.vb
Public Class SingletonViewBase
    Private _lifetimeMgr As ILifetimePolicy

    Friend Sub SetLifetime(ByVal lifetimeMgr As ILifetimePolicy)
        _lifetimeMgr = lifetimeMgr
    End Sub

    <System.Diagnostics.DebuggerNonUserCode()> _
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        Try
            If disposing AndAlso components IsNot Nothing Then
                components.Dispose()
            End If
            ' singletonオブジェクトを破棄
            ' これやるとDisposeが呼ばれてStackOverflowを起こす
            'If _lifetimeMgr IsNot Nothing Then
                '_lifetimeMgr.RemoveValue()
                '_lifetimeMgr = Nothing
            'End If
        Finally
            MyBase.Dispose(disposing)
        End Try
    End Sub
End Class

GetViewOfSingletonメソッドで生成したビューがSingletonViewBase継承していた場合はILifetimePolicyのインスタンスを設定しておき、SingletonViewBaseがDisposeされたタイミングILifetimePolicyRemoveValueメソッドを呼び出す事で、DIコンテナ上からもインスタンスが削除されるようになっている。

これで次からは同じviewNameでも新しいインスタンスが生成されるようになる。

ビューがSingletonになる可能性ができたことでPresenterExtension側にも対応が必要になったけど、その部分は割愛しておく。

とまぁ、こんな感じでビューを生成して画面遷移していけばいいと思う。

ソース

参照

*1:画面遷移するにはこの○○インターフェースを実装して、この○○メソッドを呼び出す必要があります〜なんてのは論外

*2デフォルトではFriendアクセス

*3:以前のMainView