none
循環参照について RRS feed

  • 質問


  • こんにちは。
    C# の開発において、ライブラリーやプロジェクトの循環参照は、そもそもやるべきことではない、というのを前提に質問です。
    (もちろんやる予定もまったくありません)

    プロジェクト同士の循環参照はいけないものだ、ということを MSDN などで Microsoft が説明しているところはあるでしょうか。
    「循環参照なんてするな! 終わり」「する時点で設計がいけてない」と一様の結論が出ているのですが、興味本位で探しています。


    ダメな理由は、
    ・相互に構築要素になっているのは設計的に不自然
    ・ビルドの時点で、お互いが構成要素になっているのでコンパイラが困る (現実的な問題として、不正な動作をしかねない)

    のようなことでしょうか。

    よろしくお願いします。

    近いスレッド
    (クラスライブラリの相互参照について)
    https://social.msdn.microsoft.com/Forums/ja-JP/a9568958-8989-4ad7-94e7-164cbd4f94b2?forum=csharpgeneralja


    • 編集済み ichiethel 2018年2月8日 4:31
    2018年2月8日 1:32

回答

  • C# で循環参照する DLL を作成することについての個人的な意見をまとめてみました。

    ・循環参照が絶対にダメというわけではない
    ・ただデバッグや全体のシステム構成の把握・見通しがよくなるように、循環参照を無くせる部分については無くしたほうがよい
    ・プロジェクトをビルドするときに、互いに参照があるとその時点で出来上がっていない DLL を参照することはできないので工夫が必要(参考サイト: https://blogs.msdn.microsoft.com/nickmalik/2005/03/18/how-to-get-rid-of-circular-references-in-c/

    ということだと思います。

    2018年2月8日 2:36

すべての返信

  • 表題と、本文の質問内容が異なっているのではないかとの疑いがあります。
    「プロジェクト」「ライブラリ」「DLL」等の用語の定義を確認なさった方が良いかもしれません。

    んで、「DLLの相互参照はいけないのか」についてのみ発言させてもらうと、

    DLLはそれをロードしたプロセスのアドレス空間にマップされます。
    同じプロセスから直接間接を問わず、何度ロードしても最初のマッピングのみが保持されます。
    従って、同一プロセス内で、特定のDLLを相互参照していても、それらのDLLは、
    最初にマップされたアドレスに固定されており、唯一性が保障されています。
    従って、

    (1)ネイティブなコードのDLLの相互参照には一般に何の支障もない。

    と言えます。
    OSはほぼ全てがDLLで構成されているので、そうでなければ使い物にならないことが自明ですよね。
    また、しろうとさんでなければ相互に参照しあうDLLという設計はしません。
    それは、なぜDLLにしたのかという動機と矛盾するからですね。

    2018年2月8日 2:07
  • C# で循環参照する DLL を作成することについての個人的な意見をまとめてみました。

    ・循環参照が絶対にダメというわけではない
    ・ただデバッグや全体のシステム構成の把握・見通しがよくなるように、循環参照を無くせる部分については無くしたほうがよい
    ・プロジェクトをビルドするときに、互いに参照があるとその時点で出来上がっていない DLL を参照することはできないので工夫が必要(参考サイト: https://blogs.msdn.microsoft.com/nickmalik/2005/03/18/how-to-get-rid-of-circular-references-in-c/

    ということだと思います。

    2018年2月8日 2:36
  • > 同じプロセスから直接間接を問わず、何度ロードしても最初のマッピングのみが保持されます。

    ???

    1: kd> !process 0 0 explorer.exe
    PROCESS a3c44040  SessionId: 1  Cid: 0f68    Peb: 02611000  ParentCid: 0f38
        DirBase: bfff8cc0  ObjectTable: a79f0b40  HandleCount: 2105.
        Image: explorer.exe
    
    1: kd> .process /p /r a3c44040
    Implicit process is now a3c44040
    .cache forcedecodeuser done
    Loading User Symbols
    ....
    ....
    
    
    1: kd> lmi
    start    end        module name
    000e0000 00431000   Explorer   (deferred)             
    08c00000 08c91000   UIRibbonRes   (deferred)             
    51e40000 529a9000   ieframe    (deferred)             
    58e20000 58f3f000   werconcpl   (deferred)             
    58f40000 59066000   wscui      (deferred)             
    ....
    ....
    ....
    ....
    
    Unloaded modules:
    88230000 8826c000   WdFilter.sys
    a17a0000 a17a7000   parvdm.sys
    888d0000 888db000   dump_storport.sys
    88900000 88919000   dump_LSI_SAS.sys
    88940000 88956000   dump_dumpfve.sys
    8fff0000 90023000   WUDFRd.sys
    88ad0000 88ae2000   dam.sys 
    880e0000 880ec000   WdBoot.sys
    88ff0000 88ffc000   hwpolicy.sys
    72c60000 72c78000   resourcepolicyclient.dll
    701c0000 70216000   ncsi.dll
    6acb0000 6acd7000   BluetoothApis.dll
    64fb0000 64fd7000   BluetoothApis.dll
    64f80000 64f90000   ploptin.dll
    64f80000 64f90000   ploptin.dll
    64f80000 64f90000   ploptin.dll
    64f80000 64f90000   ploptin.dll
    64e30000 64e40000   ploptin.dll
    5cc10000 5cd4b000   imjplmp.dll
    5a350000 5a3c4000   authui.dll
    58b00000 58b41000   audiodev.dll
    588e0000 58afc000   WMVCore.DLL
    63860000 6389f000   WMASF.DLL
    587d0000 588d7000   mfperfhelper.dll
    5c9b0000 5cc02000   gameux.dll
    59070000 59243000   wpdshext.dll
    6d3d0000 6d3e0000   ploptin.dll
    6d3d0000 6d3e0000   ploptin.dll
    6d3d0000 6d3e0000   ploptin.dll
    72240000 7225e000   shacct.dll
    72250000 72260000   ploptin.dll
    71720000 71730000   ploptin.dll
    5da00000 5da10000   ploptin.dll
    659d0000 659dd000   execmodelproxy.dll
    66830000 66964000   Windows.Globalization.dll
    5f8f0000 5f914000   globinputhost.dll
    5ec50000 5eca1000   Windows.ApplicationModel.LockSc
    55cb0000 55cc4000   CapabilityAccessManagerClient.d
    65950000 659c3000   Windows.System.Launcher.dll
    697c0000 697c7000   WpPortingLibrary.dll
    65940000 6594c000   dsclient.dll
    65250000 6525e000   NetworkItemFactory.dll
    5aca0000 5accb000   smartscreenps.dll
    65220000 6522b000   dtsh.dll
    5ed00000 5ed74000   lockappbroker.dll
    73680000 736e0000   FirewallAPI.dll
    6cde0000 6ce0f000   FWPolicyIOMgr.dll
    73620000 73644000   fwbase.dll
    5d8d0000 5d8f0000   EhStorAPI.dll
    593e0000 5942d000   PlayToDevice.dll
    69600000 69695000   Windows.Web.dll
    6e110000 6e120000   ploptin.dll
    6e110000 6e120000   ploptin.dll
    

    2018年2月8日 2:44
  • 回答ありがとうございます。

    たしかに、自分の記述が DLL でファイルになっていたり、開発のプロジェクトになっていたりすると、単にまずかったです。
    すいません。自分の書き方に一貫性がありませんでした。

    kenjinote さんの回答がシンプルでわかりやすかったです。

    仲澤さんの回答もよくわかるものでした。
    同じプロセスからのモジュールのロードとアンロードに追記コメントがあったので、同じdll でも、アドレスが異なることはあるのかなぁ、という気はしました。

    ありがとうございました。

    2018年2月8日 4:30
  • プロジェクトをビルドするときに、互いに参照があるとその時点で出来上がっていない DLL を参照することはできないので工夫が必要

    これはinterfaceを介することで循環参照を解消する手法ですよね? 実は.NET Framework自身も循環参照しています。System.Stringなどプリミティブな型はmscorlib.dllに定義されていますが、mscorlib.dllはXML絡みのためにSystem.XML.dllを参照しています。もちろんSystem.XML.dllはプリミティブな型を必要とするためmscorlib.dllを参照し…ガチな循環参照ができあがっています。
    おかげで.NET Frameworkランタイムは一筋縄ではいかないビルド方法になっているそうです。

    個人的にはクラス間の循環参照は構いませんが、それらすべてを一つのアセンブリ(.DLL or .EXE)に収めることをお勧めします。この場合、C#コンパイラは循環参照を解決した上でアセンブリを生成してくれます。逆に言うと、用もなく気分だけでアセンブリを分割しないほうがいいでしょう。

    2018年2月8日 8:28