DOBON.NET DOBON.NETプログラミング掲示板過去ログ

親の親 祖父クラスに変更を加えたい場合。

環境/言語:[VB2010を使用しています。]
分類:[.NET]

クラスの継承階層が以下のようになっている場合、目的の動作を行うにはどうしたら良いでしょうか?

AAA.BBB ← ここに影響を加える
AAA.BBB.CCC
AAA.BBB.CCC.DDD ← ここに反映させたい
AAA.BBB.CCC.EEE ← ここに反映させたい
AAA.BBB.CCC.FFF ← ここに反映させたい

具体的にどういう状況かと言いますと、
データグリッドビューのカラムである、
DataGridViewColumnというクラスがあります。
さらにそれを継承した、
DataGridViewTextBoxColumn
DataGridViewLinkColumn
DataGridViewButtonColumn
などのクラスが標準で用意されています。
DataGridViewColumnを継承した全てのカラムに新しいプロパティを追加したかったので、
DataGridViewColumnクラスを継承した、以下のようなクラスを作成しました。

Public Class DataGridViewColumnEX
    
    Inherits DataGridViewColumn

    Private newPropertyValue As String
    Public Property NewProperty() As String
        Get
            Return newPropertyValue
        End Get
        Set(ByVal value As String)
            newPropertyValue = value
        End Set
    End Property
    
End Class

そしていざ、このNewPropertyが追加されたDataGridViewTextBoxColumnを作ろうと思ったのですが、
以下のような問題が発生しました。

Public Class DataGridViewTextBoxColumnEX
    'Inherits DataGridViewColumnEX ' ←これを継承しないとNewPropertyが追加されない。
    'Inherits DataGridViewTextBoxColumn '←これを継承しないとテキストボックス型のカラムにならない。
End Class

さらに、上記の問題が解決した場合、NewPropertyを参照するために、
DataGridViewを継承したクラスでColumnsプロパティを使って、
Me.Columns(0).NewPropertyのように参照したいのですが、
DataGridView.ColumnsプロパティはDataGridViewColumn型を格納しているDataGridViewColumnCollection型のため、
新しく作成したNewPropertyを参照することができません。
これを解決するにはおそらく、DataGridViewColumnEX型を格納するDataGridViewColumnCollectionEXが必要になると思います。

プロパティを1つ追加するだけで大変な作業が必要なようです;
まとめとして、以下のような構造を目指しています。

'ColumnsプロパティをDataGridViewColumnCollectionEX型にしなければならない。
Public Class DataGridViewEX
    Inherits DataGridView
End Class

'Itemの型をDataGridViewColumnEX型にしなければならない。
Public Class DataGridViewColumnCollectionEX
    Inherits DataGridViewColumnCollection
    Public Sub New(ByVal _parentDataGridView As DataGridViewEX)
        MyBase.New(_parentDataGridView)
    End Sub
End Class

'このクラスは多分これで良い。
Public Class DataGridViewColumnEX
    Inherits DataGridViewColumn

    Private newPropertyValue As String
    Public Property NewProperty() As String
        Get
            Return newPropertyValue
        End Get
        Set(ByVal value As String)
            newPropertyValue = value
        End Set
    End Property
End Class

'継承したいクラスが複数ある。
Public Class DataGridViewTextBoxColumnEX
    'Inherits DataGridViewColumnEX
    'Inherits DataGridViewTextBoxColumn
End Class
■No29256に返信(ろいかえさんの記事)

Public Interface IDataGridViewColumnEx
    Property NewProperty As String
End Interface

Public Class DataGridViewTextBoxColumnEX
    Inherits DataGridViewTextBoxColumn
    Implements IDataGridViewColumnEx

    Public Property NewProperty As String Implements IDataGridViewColumnEx.NewProperty
        Get

        End Get
        Set(value As String)

        End Set
    End Property
End Class

Public Class DataGridViewLinkColumnEx
    Inherits DataGridViewLinkColumn
    Implements IDataGridViewColumnEx

    Public Property NewProperty As String Implements IDataGridViewColumnEx.NewProperty
        Get

        End Get
        Set(value As String)

        End Set
    End Property
End Class

多重継承はインターフェースで実装です。
継承でなければなりませんか?
DataGridViewColumn には任意のデータを格納できる Tag プロパティが用意されていますが、これを使用しては?
■No29257に返信(shuさんの記事)
多重継承はインターフェースで実装することができました。
ありがとうございます。

■No29260に返信(Hongliangさんの記事)
継承でなければならないということはありません。
現状は仰るとおりTagを使っていますが、
実装したいプロパティが1つではないので、Tagだけでは厳しいです。


あとはここで作ったカラムクラスのメンバに、
DataGridViewを継承したクラスのColumnsプロパティからアクセスできたら問題ありません。

Columnsプロパティに限りませんが、こうしたコレクションプロパティの型を変更する方法がよくわかりません。

DataGridViewExにPublic Property ExColumnsという新しいプロパティを作る方法も考えましたが、
その場合でもDataGridViewColumnCollectionの機能が使いたいので、
DataGridViewColumnCollectionを継承したコレクションクラスを作成する必要があり、
DataGridViewColumnCollectionのItemsの型を変更する必要があるようです。
> 現状は仰るとおりTagを使っていますが、
> 実装したいプロパティが1つではないので、Tagだけでは厳しいです。
適当にクラス作って、そのインスタンスを設定すれば済む話では?

> Columnsプロパティに限りませんが、こうしたコレクションプロパティの型を変更する方法がよくわかりません。
不可能です。
■No29263に返信(ろいかえさんの記事)

NewPropertyにアクセスしたいなら
IDataGridViewColumnExにキャストすればいいですよ?
実装してない項目は当然だめですが、実装している項目はキャストできます。
混ざってて判断しなければならないならTypeOFかTryCastを使うといいです。
For EachでまわすならOfType(Of IDataGridViewColumnEx)でもよいです。
■No29264に返信(Hongliangさんの記事)
> 適当にクラス作って、そのインスタンスを設定すれば済む話では?
これも考えましたが、Tagを多用すると可読性や保守性が下がる気がするのであまり使いたくありません;

>>Columnsプロパティに限りませんが、こうしたコレクションプロパティの型を変更する方法がよくわかりません。
> 不可能です。
なるほど。
では、似たような機能を持った別名のコレクションを作るか、
コレクションから取得したクラスをキャストするなどして対処致します。

■No29265に返信(shuさんの記事)
>NewPropertyにアクセスしたいなら
>IDataGridViewColumnExにキャストすればいいですよ?
インターフェースにキャストすることができるのは知りませんでした。
おかげ様で目的の処理を実装することができました。ありがとうございます。

しかしインターフェースの性質上、
インターフェースを継承した全てのクラスで、
インターフェースメンバを定義しなければならないのでしょうか。
なんとかして定義も共有する方法はないのでしょうか…
これは多重継承のできないVB.Netの弱点と割り切るしかないのでしょうか?
2011/10/20(Thu) 00:56:15 編集(投稿者)

> ■No29265に返信(shuさんの記事)
> しかしインターフェースの性質上、
> インターフェースを継承した全てのクラスで、
> インターフェースメンバを定義しなければならないのでしょうか。

インターフェースの定義からすれば、必然です。
実装を伴ったら、それは(ここでいう)インターフェースではありません。


> なんとかして定義も共有する方法はないのでしょうか…
> これは多重継承のできないVB.Netの弱点と割り切るしかないのでしょうか?

C# も同じ状況ですが、方法はありません。
DataGridView からすべて自作するなら、間の抽象クラスの変更ができるでしょうけれど。

多重継承ができないことが弱点かどうかは何とも言いかねますが、多重継承するとコードを読む立場としては混乱を招きます。
継承でできることは、委譲でもできるはずですので、共通となる部分を実装した小さなクラスを用意して、インターフェースを実装するクラスはその小さなクラスに中継だけさせては?
■No29266に返信(ろいかえさんの記事)

> なんとかして定義も共有する方法はないのでしょうか…
ちょっと強引ですがExtensionを使用して

呼び出し:
        Column1.NewValue.Value = "1"
        Column2.NewValue.Value = "2"

        Dim a = Column1.NewValue.Value
        Dim b = Column2.NewValue.Value


定義:

Imports System.Runtime.CompilerServices

Public Module DataGridViewColumnExMod
    Private m_NewValues As Dictionary(Of DataGridViewColumn, clsNewValue) = Nothing

    <Extension()>
    Public Function NewValue(gcol As DataGridViewColumn) As clsNewValue
        Dim Ret As clsNewValue = Nothing

        If gcol Is Nothing Then
            Return New clsNewValue()
        End If

        If m_NewValues Is Nothing Then
            m_NewValues = New Dictionary(Of DataGridViewColumn, clsNewValue)
        Else
            m_NewValues.TryGetValue(gcol, Ret)
        End If

        If Ret Is Nothing Then
            Ret = New clsNewValue
            m_NewValues.Add(gcol, Ret)
            AddHandler gcol.Disposed, AddressOf gcol_disposed
        End If

        Return Ret
    End Function

    Private Sub gcol_disposed(sender As Object, e As EventArgs)
        m_NewValues.Remove(DirectCast(sender, DataGridViewColumn))
        If m_NewValues.Count = 0 Then
            m_NewValues = Nothing
        End If
    End Sub
End Module

Public Class clsNewValue
    Public Property Value As String = String.Empty
End Class
■No29270に返信(Azuleanさんの記事)
■No29271に返信(shuさんの記事)

デリゲート、拡張メソッド、どちらも知らない機能だったのでとても勉強になりました。

しかし今回一番参考になったのは、
Public Property Value As String = String.Empty
の1行でプロパティの定義ができるということです。
プロパティの中身を書かなくても、自動的に
    Private _NewProperty As String
    Public Property NewProperty() As String
        Get
            Return _NewProperty
        End Get
        Set(ByVal value As String)
            _NewProperty = value
        End Set
    End Property
のような処理をしてくれるということを今まで知りませんでした。
これならばプロパティの内部処理を間違って書くことがないので、
単純な取得と設定だけのプロパティは以下のように1行で書くことに致します。

Public Interface IDataGridViewColumnEx
    Property NewProperty As String
End Interface

Public Class DataGridViewTextBoxColumnEx
    Inherits DataGridViewTextBoxColumn
    Implements IDataGridViewColumnEx
    Public Property NewProperty As String = String.Empty Implements IDataGridViewColumnEx.NewProperty
End Class

Public Class DataGridViewLinkColumnEx
    Inherits DataGridViewLinkColumn
    Implements IDataGridViewColumnEx
    Public Property NewProperty As String = String.Empty Implements IDataGridViewColumnEx.NewProperty
End Class

問題はプロパティ内で別のメソッドや変数にアクセスするような場合は1行で書けないので、
Azuleanさんやshuさんに提示していただいた方法を使うことになります。
拡張メソッドについては、調べて大体把握したのですが、
(この場合は拡張メソッドで値保持用クラスのインスタンスを取得して、値保持用クラスのプロパティにアクセスする方法ですね)
デリゲートについては、プロパティが委譲できないらしいので、どこをデリゲートして良いのかわかりません;
(いろんな構造を考えたのですが、どうしてもNewPropertyを共有化させる方法が思いつきません)

DOBON.NET | プログラミング道 | プログラミング掲示板