概要

.NET Frameworkもリリースから10年以上の時を経て、利用され方もずいぶんと変わってきました。そういう変化に対応するために、「小細工」的な機能をいくつか持っていたりします。ここでは、そんな小細工の中から、参照アセンブリと型転送の仕組みを紹介します。

(書きかけ)

Visual Studio での開発時と、実行時では実は(メタデータ/型情報を)参照してるアセンブリが違うという話。

実環境がなくても開発可能な理由。

PCL(Portable Class Library)の実現方法。

一枚板なフレームワーク

しばらくは、歴史的経緯の説明になります。

.NET Framework 1.0が出た当時、.NETの標準ライブラリは、ほとんどのクラスがmscorlib.dllというコア機能を提供する1つのライブラリの中に入っていました。

なんでもかんでも1つのdllに

(話をわかりやすくするために実際の.NET Frameworkの事情より簡素化して説明しています。 実際には、System.WindowsSystem.Webのクラスの多くはちゃんと別のdllに分離されています。 しかし、GUIフレームワーク向けなのにmscorlib側にあるクラスや、その逆パターンなんかもありました。 また、System.WindowsSystem.Webは分離されていましたが、 その他は割りかし本当にmscorlibに詰め込まれていました。 また、dll的には分かれていても、1つのインストーラーですべてのdllをインストールしていたので、結局、分離性はあまり良くありません。)

こういう、すべてが1つのものにつめ込まれている状態は、一枚板(monolithic)であると言われていて、あまりよい状態ではありません。(一枚板な作りにも、ファイルをわけない分オーバーヘッドが小さいという利点はあるんですが、今となっては欠点の方が目立っています。)

クライアントOS上にサーバー機能が載っていたり、その逆であったり、余計なものが含まれているという状態には、例えば以下のような問題があります。

  • 必要なストレージ サイズが増える
    • 特に、モバイル端末では数十MBの容量増加が結構致命的な差になったりする
  • 使いもしない機能のためにアップデートがかかったりする
    • 特に、アップデート後に再起動を求められたりしすると、ユーザーとしてはかなりのストレス

そこで、以下のように、クライアント向けとサーバー向けで標準ライブラリを分けて欲しいという要望が出てきます。

クライアント向けとサーバー向けを分ける

.NETチームはこの要望に応えて、実際これに近いことをやりました(補足参照)。

その結果、標準ライブラリにいくつかのバリエーションができることになりました。この標準ライブラリのバリエーションをプロファイル(profile)と呼びます。

さて、ここで、2つの課題が残ります。

  • フル プロファイル版を入れてしまった開発機で、クライアント プロファイルの開発はどうやるべきか。
  • そもそもコア ライブラリ(mscorlib)になんでもかんでも詰まりすぎ。分離が必要。

以降で、これらの解決策について説明していきます。

補足: 実際の「プロファイル」

より正確に、.NETの歴史で起きたことを補足的に書いておきます。

  • .NET 3.5には、サーバー機能を外した「クライアント プロファイル」というバージョンがある。
    • この時期特に、.NET Frameworkのインストール サイズがかなり肥大化していて、エンド ユーザーに嫌われていたため、このバージョンが生まれました。
    • ただ、サイズ肥大化のかなりの理由がx86, x64, IA64向けを1つのインストーラーに入れていたせいで、IA64サポートの取りやめと、x86, x64版の分離でかなりサイズが小さくなってしまったので、今となっては「クライアント プロファイル」の存在意義はなくなっています。
    • また、最近(2015年前後)は、クライアント向けには.NET Nativeという別系統の最適化技術を使おうとしているので、なおのこと「クライアント プロファイル」の意味がなくなっています。
  • Windows 8以降、GUI周りの機能は、.NET実装ではなく、C++で実装されたWinRTという別のフレームワークを使う方針に転換しました。
    • その結果、「コア プロファイル」という、クライアント機能もサーバー機能も削った最小限の.NET Frameworkが出来ました
  • サーバー向けの最適化(クライアント向け機能の削除)は.NET Core 5(Windows 10やVisual Studio 2015の世代の技術)で初めて登場しました。
    • Windows は長らくサーバー製品でもGUIが標準で入っていて、その結果、サーバー向けの.NETにもサーバー向け機能が含まれていました。
    • Windowsサーバー製品にも、Nano ServerというGUIが完全に除外されたバージョンが登場して、事情が変わりました。

プロファイル

.NET にもいろいろなバージョンが出てて。 でも、主な差はプロファイルの差。

別ページ(「実行基盤」 )でも説明しているけども

いろいろな .NET

いろいろな .NET

最初にこういう「プロファイルの差」が出始めたのは、.NET 2と3がin-placeだったからだっけ? Clinet Profileってのができたのも。

で、開発機と配布先でのプロファイルの差が問題に

プロファイルの差による問題

プロファイルの差による問題

Full .NETが入ってるマシンで Client Profile ターゲットの開発するために 解決策として導入された方法が、参照アセンブリ。

参照アセンブリ

実行時に見てるのと、Visual Studio 上で参照してるのが違う。 リフレクションで実行時に見てるのが何かを調べて出す例を VS 上のはプロパティ ウィンドウで。

参照アセンブリの場所

参照アセンブリの場所

実行時に見てるのは

using System;

class Program
{
    static void Main()
    {
        Console.WriteLine(typeof(int).Assembly.Location);
        Console.WriteLine(typeof(Uri).Assembly.Location);
    }
}
C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.dll
C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll

Visual Studio から見ているアセンブリの中身をのぞくと

参照アセンブリの中身

参照アセンブリの中身

中身からっぽ。 つまり、実際に動かせるアセンブリじゃなくて、 メタデータ(どういうクラスがあって、どういうメソッドを持っているか)を参照するためだけのもの。

これで、Full プロファイルが入っている開発環境で、 Client プロファイルをターゲットにした開発とかができる。

Windows ストア アプリ

実は、Windows ストア アプリからも、実行時にはデスクトップ版と全く同じアセンブリを参照してる。

Visual Studio 上からは、参照アセンブリだけ切り替えて別プロファイルに見せかけてる。 リフレクションを使えばデスクトップ版で使える全機能を使える。

そんなことしてまでわざわざ制限かけてるのは、セキュリティ的理由と、将来的にサポートできなくなる可能性を今のうちから避けるという理由。

今は、実装手間を考えて「実は同じものを見てる」実装になってるけども。ずっとそうである保証ない。 将来的に、レガシー削除してしまっている Core プロファイル(Windows ストア アプリから参照してる方)の方がサポート期間が長かったりする可能性ある。

Portable Class Library

今は、もっとプロファイルが増えてる Silverlight、Windows Store Apps、Windows Phone、… Client プロファイルと Full プロファイルみたいに単純な包含関係(上位互換)でもない。

PCL: プロファイルでターゲットを切り替えれるものの、1度ターゲットを決めたらそれ以外で使えなくなった。 それじゃまずいんで、PCL 作った。

共通部分を作って、フラグ的に管理。 まあ、ぶっちゃけ、{ A, B, C } に対して、{ A, B, C, A&B, B&C, C&A, A&B&C } みたいな参照アセンブリを作って置いておく仕組み。 ターゲット パックってのを入れたらターゲット増やせる。

余談: 型フォワーディング

型フォワーディング(type forward)

「同じクラスを持っているのに PCL で使えない」みたいな状況もあって。 .NET は「クラスと、そのクラスがどこのアセンブリで定義されているか」を見てコードを実行しているので、 型名、メンバー構成が一緒なだけじゃだめで、どのアセンブリで定義されてるかも一致してないとダメ。 「同じクラスが別のアセンブリで定義されている」とかだと PCL で使えない。

例えば、ObservableCollection<T>。 WindowsBase.dll → System.dll/System.Windows.dll → System.ObjectModel.dll。

これじゃ PCL の利便性的にいまいちなので、 .NET 4.5 世代で整理しまくった(クラスを定義するアセンブリを大規模変更)。

一方で、過去との互換性を取るために、以前のアセンブリでも同名のクラスが定義されているかのように見せかける仕組みが必要。 それが TypeForwardedTo 属性。 .NET 4.5 の System.dll の中身のぞくと大量に。

余談ついでに、.NET 4.5 のアセンブリ整理、DLL 間の相互依存が減るように整理されてる。