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

異なるプロジェクト間で基底・派生クラス作成時、基底クラスが参照できない

環境/言語:[VB.NET2005、VB.NET2008]
分類:[.NET]

VB.NETにおいてクラスライブラリを作成中です。

開発環境は、同一ソリューション内に目的ごとに分けた複数のプロジェクトを
作成し個別のdll化しようと考えています。

プロジェクトの分け方の例で言うと

・ベースプロジェクト[clsBase](基底クラス)
    共通使用クラスの定義(構造体の代わりに使用)
    列挙型の定義
    基本になるメソッドなど

・サブプロジェクト[clsSub](clsBaseからの派生クラス)
                        :
                        :

こんな感じです


そこで、実際に下記のような構成でプロジェクトを作成し、ユーザクラス上に
派生クラスのインスタンスを作成してみましたが、そのインスタンスから
基底クラスのメソッドなどが参照できません...ちょっと手詰まり状態です。
※基底・派生クラスが別々のプロジェクトにあるケースのみ参照不可



・ベースプロジェクト[clsBase](基底クラス)
ルート名前空間:baseclass
--- clsBase.vb ---

Public Class clsBase
    Public Function GetStringBase() As String
        Return "GetStringBase()"
    End Function
End Class




・サブプロジェクト[clsSub](clsBaseからの派生クラス)
ベースプロジェクトを参照追加

ルート名前空間:subclass
--- clsSub.vb ---
Public Class clsSub
    Inherits baseclass.clsBase

    Public Function GetStringSub() As String
        Return "GetStringSub()"
    End Function

End Class

Public Class clsOverSub2
    Inherits clsSub

    Public Function GetStringOverSub2() As String
        Return "GetStringOverSub2()"
    End Function

End Class


--- clsOverSub.vb ---
Public Class clsOverSub
    Inherits clsSub

    Public Function GetStringOverSub() As String
        Return "GetStringOverSub()"
    End Function

End Class




・ユーザプロジェクト[clsUser](通常のクラスライブラリプロジェクト)
サブプロジェクトを参照追加

ルート名前空間:userclass
--- UserClass.vb ---
Imports subclass

Public Class UserClass

    Public Function aaa() As String
        Dim szStr As String
        Dim sc As New subclass.clsSub()
        Dim soc As New subclass.clsOverSub()    ----- clsSub.vb内に記述した派生クラス
        Dim soc2 As New subclass.clsOverSub2()    ----- clsSub.vbとは別のclsOverSub.vb内に記述した派生クラス


        szStr = sc.GetStringBase() & vbCrLf            ----- 基底クラスメソッド参照不可

        szStr = sc.GetStringSub() & vbCrLf            ----- 派生クラスメソッド参照:○

        szStr = soc.GetStringSub() & vbCrLf            ----- 基底クラスメソッド参照:○

        szStr = soc.GetStringOverSub() & vbCrLf        ----- 派生クラスメソッド参照:○

        szStr = soc2.GetStringSub() & vbCrLf        ----- 基底クラスメソッド参照:○

        szStr = soc2.GetStringOverSub2() & vbCrLf    ----- 派生クラスメソッド参照:○

        Return szStr
    End Function

End Class


ここで、基底クラスと派生クラスの名前空間の違いの問題?かとも思い
派生クラス側のプロジェクトのルート名前空間をbaseclassに変えてみましたが、
同じ結果でした(参照設定は適宜変更)


尚、基底クラスと派生クラスを同一プロジェクト内に作成すると
ユーザクラスで作成したインスタンスで基底クラス・派生クラスの両方の
メソッドが参照できました。

しかし、それぞれを別のDLLで管理したいため、プロジェクトは分けた方向で考えたいので困っております

たぶん、基本中の基本のレベルのような気もしますが...
おのでらです。

ユーザプロジェクトにベースクラスのプロジェクトを参照に追加し、ベースプロジェクトの名前空間を Import するとどうでしょうか
■No25964に返信(おのでらさんの記事)
> おのでらです。
>
> ユーザプロジェクトにベースクラスのプロジェクトを参照に追加し、ベースプロジェクトの名前空間を Import するとどうでしょうか

おのでらさん、早速の返信ありがとうございます。

確かに、ユーザプロジェクト側で、意図的にベースプロジェクトの参照を追加することで
インスタンスから基底クラスのメソッドは参照できるようにはなりました。

ただ、ということは、この手のクラスライブラリを使用する際、ユーザ(使い手側)は
常に使用したいクラスの基底クラスを知っておいて、自作プロジェクトに参照追加
しなくてはならないということになるのでしょうか....

使いづらくなってしまいますね

.NET Framework自体もこのような構成になっているのではないかと思うんですが
どのように実装しているのかわかればな
■No25967に返信(わんこさんの記事)
> .NET Framework自体もこのような構成になっているのではないかと思うんですが
> どのように実装しているのかわかればな

.NET のクラスライブラリの設計指針は以下のドキュメントに基づいてます。

クラス ライブラリ開発のデザイン ガイドライン
http://msdn.microsoft.com/ja-jp/library/ms229042.aspx

かなり膨大なドキュメントですが、
クラス設計の指針として一度目を通しておくと、
エンジニアとして一皮も二皮もむけてくると思います。
■No25969に返信(ひらぽんさんの記事)
> ■No25967に返信(わんこさんの記事)
>>.NET Framework自体もこのような構成になっているのではないかと思うんですが
>>どのように実装しているのかわかればな
>
> .NET のクラスライブラリの設計指針は以下のドキュメントに基づいてます。
>
> クラス ライブラリ開発のデザイン ガイドライン
> http://msdn.microsoft.com/ja-jp/library/ms229042.aspx

こんにちは、ひらぽんさん
ガイドラインは先程から眺めてみてます。
ガイドラインという意味では、「なるほど!こういう指針でもって作れってことね」という目で見ています。
しかし、今回の実際の疑問を直接解決できるような項目を見つけるには至っておりません

ただ、文献としてはブックマークして後々参考にしたいと思います。

MSDNやTecNetも今から漁ってみます
■No25973に返信(わんこさんの記事)
■No25967に返信(わんこさんの記事)
> ただ、ということは、この手のクラスライブラリを使用する際、ユーザ(使い手側)は
> 常に使用したいクラスの基底クラスを知っておいて、自作プロジェクトに参照追加
> しなくてはならないということになるのでしょうか....
> 
> 使いづらくなってしまいますね

普通、ツリー状に実装するので大して使いにくくはならないかと思います。
変な事すると循環参照の罠にかかるので注意する必要がありますしね。

管理するだけ(コード実装しない)のであれば、クラスではなくインターフェイスのが良いです。

---------------------------
◇ 基本ライブラリ
   名前空間 : ObjectLibrary.ObjectModel 
   アセンブリ名 : base.dll

Public Interface IBase
    Function GetStringBase() As String
End Interface

---------------------------
◇ サブライブラリ1
    名前空間 : ObjectLibrary
    アセンブリ名 : sub1.dll
    参照 : base.dll

Imports ObjectLibrary.ObjectModel

Public Class Sub1
    Implements IBase

    Public Overridable Function GetStringBase() As String Implements IBase.GetStringBase
        Return "Sub1"
    End Function
End Class

---------------------------
◇ サブライブラリ2
    名前空間 : ObjectLibrary
    アセンブリ名 : sub2.dll
    参照 : base.dll

Imports ObjectLibrary.ObjectModel

Public Class Sub2
    Implements IBase

    Public Function GetStringBase() As String Implements IBase.GetStringBase
        Return "Sub2"
    End Function
End Class

---------------------------
◇ 最終利用 (コンソールプロジェクト)
    参照 : base.dll, sub1.dll, sub2.dll

Imports ObjectLibrary.ObjectModel
Imports ObjectLibrary

Sub Main()

    ' インターフェイスで変数を作成
    Dim base As IBase = Nothing

  ' サブクラス1をキャスト
    base = New Sub1()
    Console.WriteLine(base.GetStringBase())

  ' サブクラス2をキャスト
    base = New Sub2()
    Console.WriteLine(base.GetStringBase())

End Sub
---------------------------

ちなみに…
インターフェイスは複数実装できるので、複数のインターフェイスを掛け合わせて作ることもできます。

---------------------------
◇ 基本ライブラリ ( に追加 )
   名前空間 : ObjectLibrary.ObjectModel 
   アセンブリ名 : base.dll

Public Interface IBase2
    Function GetStringOverSub() As String
End Interface

---------------------------
◇ サブライブラリ3
    名前空間 : ObjectLibrary
    アセンブリ名 : sub3.dll
    参照 : base.dll, sub1.dll

Imports ObjectLibrary.ObjectModel

' 両方のインターフェイスを実装
Public Class Sub3
    Implements IBase
    Implements IBase2

    Public Function GetStringBase() As String Implements IBase.GetStringBase
        Return "Sub3"
    End Function

    Public Function GetStringOverSub() As String Implements IBase2.GetStringOverSub
        Return "OverSub3"
    End Function

End Class

' サブクラス1にインターフェイスを追加実装
Public Class Sub4
    Inherits Sub1
    Implements IBase2

    Public Overrides Function GetStringBase() As String
        Return "Sub4"
    End Function

    Public Function GetStringOverSub() As String Implements IBase2.GetStringOverSub
        Return "OverSub4"
    End Function

End Class

---------------------------
◇ 最終利用 (コンソールプロジェクト)
    参照 : base.dll, sub1.dll, sub2.dll, sub3.dll

Imports ObjectLibrary.ObjectModel
Imports ObjectLibrary

Sub Main()

    ' 各サブクラスを WriteLineメソッドで表示する
    WriteLine(New Sub1())
    WriteLine(New Sub2())
    WriteLine(New Sub3())
    WriteLine(New Sub4())

End Sub

' 表示用メソッド
Sub WriteLine(ByVal base As IBase)

    ' IBase の GetStringBase メソッドを表示
    Console.WriteLine(base.GetStringBase())

    ' IBase2が実装されているか確認。されてなければ終了。
    If (TypeOf base Is IBase2) = False Then Return

    ' IBase2 にキャストして GetStringOverSub メソッドを呼ぶ
    Console.WriteLine(DirectCast(base, IBase2).GetStringOverSub())

End Sub
---------------------------

こんな事もできますよ。って例でした。

 
> .NET Framework自体もこのような構成になっているのではないかと思うんですが
> どのように実装しているのかわかればな

ガイドラインと構成は、MSDNに書いてあるので探してみるといいかも。

単純にコードを見たいのであれば、こちらへ。(英語)
http://referencesource.microsoft.com/
追加です(笑

ほかのアセンブリのクラスを使う場合にはラッパークラスを作るのが有効です

---------------------------
◇ サブライブラリ4
    名前空間 : ObjectLibrary
    アセンブリ名 : sub4.dll
    参照 : base.dll

Imports ObjectLibrary.ObjectModel

Public Class Sub5
    Implements IBase
    Implements IBase2

    ' 普通のコンストラクタ
    Public Sub New()
        Me.New(Nothing)
    End Sub

    ' ラッピング元のインスタンスを指定したコンストラクタ
    Public Sub New(ByVal baseInstance As IBase)
        Me.baseInstance = baseInstance
    End Sub

    ' ラッピング元のインスタンス
    Private baseInstance As IBase

    Public Function GetStringBase() As String Implements IBase.GetStringBase
        If (Me.baseInstance Is Nothing) Then Return "Sub5"
        Return "Sub5 with " + Me.baseInstance.GetStringBase()
    End Function

    Public Function GetStringOverSub() As String Implements IBase2.GetStringOverSub
        If (Me.baseInstance Is Nothing Or (Not Me.baseInstance Is Nothing AndAlso (TypeOf Me.baseInstance Is IBase2) = False)) Then Return "OverSub5"
        Return "OverSub5 with " + DirectCast(Me.baseInstance, IBase2).GetStringOverSub()
    End Function

End Class

---------------------------

◇ 最終利用 (コンソールプロジェクト)
    参照 : base.dll, sub3.dll, sub4.dll

Imports ObjectLibrary.ObjectModel
Imports ObjectLibrary

Sub Main()

    ' 各サブクラスを WriteLineメソッドで表示する
    WriteLine(New Sub5())
    WriteLine(New Sub5(New Sub3()))
    WriteLine(New Sub5(New Sub4()))

End Sub

' 表示用メソッド
Sub WriteLine(ByVal base As IBase)

    ' IBase の GetStringBase メソッドを表示
    Console.WriteLine(base.GetStringBase())

    ' IBase2が実装されているか確認。されてなければ終了。
    If (TypeOf base Is IBase2) = False Then Return

    ' IBase2 にキャストして GetStringOverSub メソッドを呼ぶ
    Console.WriteLine(DirectCast(base, IBase2).GetStringOverSub())

End Sub

---------------------------
■No25962に返信(わんこさんの記事)
> しかし、それぞれを別のDLLで管理したいため、プロジェクトは分けた方向で考えたいので困っております

プロジェクトを分けた場合、そのプロジェクト内の型を使う側にも
参照設定が必要になります;レイトバインドで実装する場合は別ですが。


たとえば、Windows Forms のプロジェクトで
 Dim x = Me.AccessibilityObject
のようなコードを書いてみてください。

Control クラス、たとえばフォームやテキストボックスなどには、
AccessibilityObject というプロパティがあり、そこから
AccessibleObject オブジェクトを得られるようになっているのですが、
参照設定にこの型を持つアセンブリが登録されていない場合、
青波線と共に、参照設定不足のエラーが報告される事になります。

------------------------
実装されたインターフェイス 'Accessibility.IAccessible' を含む
アセンブリ '(略)'への参照が必要です。参照をプロジェクトに追加してください。
------------------------

# ちなみに、青波線右下のエラー修正オプション(赤線)を選択すると、
# 自動的に参照設定が行われ、エラーが解決されるようになっています。
Algolさん、こんにちは

> 普通、ツリー状に実装するので大して使いにくくはならないかと思います。
> 変な事すると循環参照の罠にかかるので注意する必要がありますしね。

確かに、そう言われるとそのようにも思います。

先の、MSが言う、ガイドラインでもあったように、ネームスペースの
付け方いかんでも、この考え方には差異が出てくると思いますし
ドットで区切ったネームスペースで、名前空間の違いが一目でわかるように
すれば、左から右に向かって派生していると考えられるので、その辺もきっちり
考慮して、クラスライブラリーを作ってみます。

ありがとうございました



ご丁寧に、コードまで記述いただいて大変感謝いたします。

ありがたく、読み解かさせていただきます。
魔界の仮面弁士さん、こんにちは


> たとえば、Windows Forms のプロジェクトで
>  Dim x = Me.AccessibilityObject
> のようなコードを書いてみてください。
>
> Control クラス、たとえばフォームやテキストボックスなどには、
> AccessibilityObject というプロパティがあり、そこから
> AccessibleObject オブジェクトを得られるようになっているのですが、
> 参照設定にこの型を持つアセンブリが登録されていない場合、
> 青波線と共に、参照設定不足のエラーが報告される事になります。
>
> ------------------------
> 実装されたインターフェイス 'Accessibility.IAccessible' を含む
> アセンブリ '(略)'への参照が必要です。参照をプロジェクトに追加してください。
> ------------------------

個人的な、考え方の差異からきている違和感のようなものがあったので、トピを〆ずに来ていましたが

おおよそ理解したしましたので、一応この問題点はフィックスしたものとします

ご回答いただいた皆様には感謝いたします
解決済み!
2009/12/04(Fri) 16:41:56 編集(投稿者)
2009/12/04(Fri) 16:40:03 編集(投稿者)

> ただ、ということは、この手のクラスライブラリを使用する際、ユーザ(使い手側)は
> 常に使用したいクラスの基底クラスを知っておいて、自作プロジェクトに参照追加
> しなくてはならないということになるのでしょうか....
> 
> 使いづらくなってしまいますね

それは仕方ないと思います。

ただし、「名前空間」−「アセンブリ」−「クラス」の関係をユーザーが意識できるよう
関連付けているなら、ユーザーもさほど混乱なくクラスを使用できると思います。


たとえば1例として現在携わっているシステムの場合。
ソリューションに以下のようにプロジェクトを設けてます。

■まず「プロジェクト名=ネームスペース名」になるよう慎重に定義します。

xxxx.Tools.Controls  汎用カスタムコントロールを提供します。
xxxx.Tools.Database  データベースとアクセス用の汎用クラスを提供します。
xxxx.Tools.Utility   アプリケーション全体で使用される汎用的な機能を提供します。

xxxx.Business.Master マスタ参照・更新用の機能を提供します。
xxxx.Business.Map    地図コンポーネントを提供します。
xxxx.Business.Sales  販売業務の参照・更新用の機能を提供します。


■そして xxxx.Tools.Controls には自作のカスタムコントロールを加えます。

// 拡張テキストボックス
xxxx.Tools.Controls.ExTextBox 
// 拡張データグリッドビュー
xxxx.Tools.Controls.ExDataGridView 


■また xxxx.Tools.Database には ADO.NET を隠蔽したクラスを設けます。

// ADO.NET をカプセル化し、簡素化したインターフェイスを提供します。
xxxx.Tools.Database.DataFacade 


■業務で使う地図機能は、xxxx.Business.Map 名前空間にまとめます。

// 配送ルートを表示する地図コントロールです。
xxxx.Business.Map.MapControl


■販売集計用の画面は当然以下のようになります。

// 販売集計用画面です。
xxxx.Business.Sales.SalesSummaryForm


こんな具合に「名前空間」に対して「クラス」が明確な意図を持った配置がなされていれば
ユーザーはどこに何があるのか、このクラスはどのアセンブリにあるのかが推測しやすくなります。

もちろん最初は存在が曖昧なクラスもありますし、どの名前空間においていいか悩むクラスも存在します。
その場合、リファクタリングを進めていく中にクラスの機能が絞れてきて
配置すべき名前空間が判ると同時に名前空間を移動したり、
状況によっては名前空間の新設や統廃合を行ったりしてます。

だから「名前」は極めて大事ですね。
クラスの名前を決めるだけで、半日悩んだりします。
ひらぽんさん、こんばんは

>>使いづらくなってしまいますね
>
> それは仕方ないと思います。
>
> ただし、「名前空間」−「アセンブリ」−「クラス」の関係をユーザーが意識できるよう
> 関連付けているなら、ユーザーもさほど混乱なくクラスを使用できると思います。

確かにおっしゃる通りです、でもって夕方からネームスペースに関して考察し
実装に入っているところです。

なかなか、この手の話はありそうなのに、実際他のトピックや掲示板に「これだ!」という記述が
無くて、TecNetのフォーラムなどにも投げてみようかとも思っていたところです

おかげさまで、私の脳内にも指針ができましたので、それに向かっていきたいと思います

回答をいただいた皆様、本当にありがとうございました。
解決済み!

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