ポータブル クラス ライブラリに関する検証
以前、「Windows Store アプリと Windows Phone アプリ、Silverlight アプリ、WPF アプリでソースコードを共通化する方法に関する記事」と云う記事でポータブル クラス ライブラリに関して少しだけご紹介した。
今回は、ポータブル クラス ライブラリについて、更に調べてみよう。
■ アプリケーションの種類と .NET Framework の違い
Windows ストア アプリで参照している .NET は、他のアプリケーションで参照しているものと少し異なる。
その様子を先ず確認してみよう。
現時点で最新の環境で何種類かのアプリケーションをデフォルトで追加してみて、参照している .NET を見てみる。
・コンソール アプリケーションとクラス ライブラリの場合 (.NET Framework 4.5)
・WPF の場合 (.NET Framework 4.5)
・Silverlight の場合 (Silverlight 5)
・Windows Phone の場合 (Windows Phone OS 8.0)
「.NET for Windows Phone」となっている。
・Windows ストア アプリの場合
こちらでは、「.NET for Windows Store apps」となっているのが判る。
■ IsSubclassOf 等による検証
つまり、これらで参照している .NET は、全てが共通な訳ではない。
コア部分は共通なのだろうか?
私が試してみたところ、System 名前空間の付近でも微妙な違いがあるようだ。
今回は、以下のようなコードを用いて、この辺りを検証してみたい。
// コンソール アプリケーション // .NET Framework 4.5 using System.Reflection; // ※ GetRuntimeProperties に必要 class Program { class Super { } class Sub : Super { public int Number { get; set; } public string Name { get; set; } } static void Test() { var sub = new Sub(); bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); bool result3 = sub is Super; var properties1 = typeof(Sub).GetProperties(); var properties2 = typeof(Sub).GetRuntimeProperties(); } static void Main() { Test(); } }
これはコンソール アプリケーションのソースコードだが、正常にコンパイルでき、正常に動作する。
Visual Studio のデバッガーで値を確認してみると、次のようになった。
・WPF と Silverlight の場合
同様のことを、WPF と Silverlight の場合で試してみると次のようになる。
先ず WPF から。
// WPF アプリケーション // .NET Framework 4.5 using System.Windows; namespace WpfApplication { using System.Reflection; // ※ GetRuntimeProperties に必要 public partial class App : Application { class Super { } class Sub : Super { public int Number { get; set; } public string Name { get; set; } } static void Test() { var sub = new Sub(); bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // ○ OK bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // ○ OK bool result3 = sub is Super; // ○ OK var properties1 = typeof(Sub).GetProperties(); // ○ OK var properties2 = typeof(Sub).GetRuntimeProperties(); // ○ OK } public App() { Test(); } } }
WPF では問題なく、全てコンパイルでき、正常に動作する。
参照している .NET が同じ .NET Framework 4.5 なので、当たり前と云えば当たり前だ。
次に Silverlight。
// Silverlight 5 using System; using System.Windows; namespace SilverlightApplication { using System.Reflection; // ※ GetRuntimeProperties に必要 public partial class App : Application { class Super { } class Sub : Super { public int Number { get; set; } public string Name { get; set; } } static void Test() { var sub = new Sub(); bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // ○ OK bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // ○ OK bool result3 = sub is Super; // ○ OK var properties1 = typeof(Sub).GetProperties(); // ○ OK var properties2 = typeof(Sub).GetRuntimeProperties(); // × コンパイル エラー } public App() { Test(); // ... 以下省略 ... } // ... 以下省略 ... } }
Silverlight の方は、次のようなコンパイル エラーになる。
- エラー 1 'System.Type' に 'GetRuntimeProperties' の定義が含まれておらず、型 'System.Type' の最初の引数を受け付ける拡張メソッドが見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。
この GetRuntimeProperties は、 System.Reflection 名前空間の RuntimeReflectionExtensions クラスが持つ拡張メソッドだ。
.NET Framework 4.5 で使えるようになったものだが、Silverlight では使えないようだ。
・Windows Phone の場合
ちなみに、Windows Phone では次のようになる。
// Windows Phone OS 8.0 using Microsoft.Phone.Controls; using Microsoft.Phone.Shell; using PhoneApp.Resources; using System.Diagnostics; using System.Windows; using System.Windows.Markup; using System.Windows.Navigation; namespace PhoneApp { using System; using System.Reflection; // ※ GetRuntimeProperties に必要 public partial class App : Application { class Super { } class Sub : Super { public int Number { get; set; } public string Name { get; set; } } static void Test() { var sub = new Sub(); bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // ○ OK bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // ○ OK bool result3 = sub is Super; // ○ OK var properties1 = typeof(Sub).GetProperties(); // ○ OK var properties2 = typeof(Sub).GetRuntimeProperties(); // ○ OK } public App() { Test(); // ... 以下省略 ... } // ... 以下省略 ... } }
全て問題なくコンパイル・実行できる。
・Windows ストア アプリの場合
さて、Windows ストア アプリではどうなるだろうか。
こうなるのだ。
// Windows ストア アプリ using System; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace WindowsStoreApp { using System.Reflection; // ※ GetRuntimeProperties に必要 sealed partial class App : Application { class Super { } class Sub : Super { public int Number { get; set; } public string Name { get; set; } } static void Test() { var sub = new Sub(); bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // × コンパイル エラー bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // × コンパイル エラー bool result3 = sub is Super; // ○ OK var properties1 = typeof(Sub).GetProperties(); // × コンパイル エラー var properties2 = typeof(Sub).GetRuntimeProperties(); // ○ OK } public App() { Test(); // ... 以下省略 ... this.InitializeComponent(); this.Suspending += OnSuspending; } // ... 以下省略 ... } }
次のようなコンパイル エラーとなる。
- エラー 1 'System.Type' に 'IsSubclassOf' の定義が含まれておらず、型 'System.Type' の最初の引数を受け付ける拡張メソッドが見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。
- エラー 2 'System.Type' に 'IsAssignableFrom' の定義が含まれておらず、型 'System.Type' の最初の引数を受け付ける拡張メソッドが見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。
- エラー 3 'System.Type' に 'GetProperties' の定義が含まれておらず、型 'System.Type' の最初の引数を受け付ける拡張メソッドが見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。
なんと、System 名前空間の Type 型が IsSubclassOf や IsAssignableFrom を持っていないようだ。
実際に調べてみると、Windows ストア アプリが参照している System 名前空間の Type 型は以下の public メンバーを持っている。
// Windows ストア アプリが参照している System 名前空間の Type 型 // アセンブリ System.Runtime.dll, v4.0.0.0 // Framework\.NETCore\v4.5\System.Runtime.dll namespace System { public abstract class Type { public static readonly object Missing; public abstract string AssemblyQualifiedName { get; } public abstract Type DeclaringType { get; } public abstract string FullName { get; } public abstract int GenericParameterPosition { get; } public abstract Type GenericTypeArguments { get; } public bool HasElementType { get; } public bool IsArray { get; } public bool IsByRef { get; } public abstract bool IsConstructedGenericType { get; } public abstract bool IsGenericParameter { get; } public bool IsNested { get; } public bool IsPointer { get; } public abstract string Name { get; } public abstract string Namespace { get; } public virtual RuntimeTypeHandle TypeHandle { get; } public override bool Equals(object o); public bool Equals(Type o); public abstract int GetArrayRank(); public abstract Type GetElementType(); public abstract Type GetGenericTypeDefinition(); public override int GetHashCode(); public static Type GetType(string typeName); public static Type GetType(string typeName, bool throwOnError); public static Type GetTypeFromHandle(RuntimeTypeHandle handle); public abstract Type MakeArrayType(); public abstract Type MakeArrayType(int rank); public abstract Type MakeByRefType(); public abstract Type MakeGenericType(params Type typeArguments); public abstract Type MakePointerType(); public override string ToString(); } }
IsSubclassOf、IsAssignableFrom、GetProperties 等が見当たらない。
一方、通常の .NET Framework 4.5 の名前空間の Type 型は遥かに多くの public メンバーを持っている。
// 通常の .NET Framework 4.5 の名前空間の Type 型 // アセンブリ mscorlib.dll, v4.0.0.0 // Framework\.NETFramework\v4.5\mscorlib.dll namespace System { public abstract class Type : MemberInfo, _Type, IReflect { bool IsSubclassOf(Type c); bool IsAssignableFrom(Type c); PropertyInfo GetProperties(); // ...その他遥かに多くのメンバー... } }
先ず、アセンブリが異なる。Windows ストア アプリの方は、 mscorlib.dll ではなく System.Runtime.dll だった。
また、こちらは Windows ストア アプリの方の Type と異なり、「class Type : MemberInfo, _Type, IReflect」となっているのが判る。
この中の _Type は実は interface で、以下のように IsSubclassOf、IsAssignableFrom、GetProperties を含む多くのメンバーを持っているのだ。
// 通常の .NET Framework 4.5 の名前空間の Type 型が実装している interface _Type namespace System.Runtime.InteropServices { public interface _Type { bool IsSubclassOf(Type c); bool IsAssignableFrom(Type c); PropertyInfo GetProperties(); // ...その他多くのメンバー... } }
■ ポータブル クラス ライブラリによる解決
では、ポータブル クラス ライブラリを利用した場合はどうなるだろうか。
・ポータブル クラス ライブラリの作成
先ず、ポータブル クラス ライブラリを作成する。
今回は、ターゲット フレームワークとしてデフォルトの儘、.NET Framework 4.5、Silverlight 4 以上、Windows Phone 7 以上、.NET for Windows Store apps を選ぶ。
ソースコードは以下の通り。
Type 型の IsSubclassOf、IsAssignableFrom、GetProperties をそれぞれ呼ぶだけのメソッドを用意することにする。
// ポータブル クラス ライブラリ // // ターゲット フレームワーク: // ・.NET Framework 4.5 // ・Silverlight 4 以上 // ・Windows Phone 7 以上 // ・.NET for Windows Store apps namespace PortableClassLibrary { using System; using System.Reflection; public static class TestClass { public static bool IsSubclassOf(Type sub, Type super) { return sub.IsSubclassOf(super); } public static bool IsAssignableFrom(Type type1, Type type2) { return type1.IsAssignableFrom(type2); } public static PropertyInfo GetProperties(Type type) { return type.GetProperties(); } } }
これは問題なくコンパイルできる。
ちなみに、このポータブル クラス ライブラリで参照している Type 型は次のようなものだ。
// このポータブル クラス ライブラリが参照している Type 型 // アセンブリ mscorlib.dll, v2.0.5.0 // Framework\.NETPortable\v4.0\Profile\Profile4\mscorlib.dll namespace System { public abstract class Type : MemberInfo { bool IsSubclassOf(Type c); bool IsAssignableFrom(Type c); PropertyInfo GetProperties(); // ...その他多くのメンバー... } }
こちらは、IsSubclassOf、IsAssignableFrom、GetProperties 等を持っている。_Type interface は持っていない。
また、このポータブル クラス ライブラリが参照している .NET はこうなっている。
「.NET Portable Subset」となっている。
・作成したポータブル クラス ライブラリの参照
では、早速このポータブル クラス ライブラリを各アプリケーション (コンソール アプリケーション、WPF アプリケーション、Silverlight アプリケーション、Windows Phone アプリ、Windows ストア アプリ) でそれぞれ参照してみよう。
そして、各アプリケーションで、以下のようにこのポータブル クラス ライブラリの三つのメソッド、IsSubclassOf、IsAssignableFrom、GetProperties を呼んでみる。
// 参照したポータブル クラス ライブラリの利用 bool result4 = PortableClassLibrary.TestClass.IsSubclassOf(sub.GetType(), typeof(Super)); bool result5 = PortableClassLibrary.TestClass.IsAssignableFrom(sub.GetType(), typeof(Super)); var properties3 = PortableClassLibrary.TestClass.GetProperties(sub.GetType());
すると、コンソール アプリケーション、WPF アプリケーション、Silverlight アプリケーション、Windows Phone アプリ、Windows ストア アプリの何れでも、コンパイルでき、正常に動作する。
つまり、ポータブル クラス ライブラリにであれば、この部分のコードは共通化でき、且つそれぞれのアプリケーションから問題なく呼べるのだ。
■ 今回のまとめ
今回は、ポータブル クラス ライブラリに関して、調査をしてみた。
アプリケーションの種類によって、参照している .NET が異なるので、.NET のコア部分を使ったソースコードでも共通のものが使えないことがあることが判った。
ポータブル クラス ライブラリを使うことで、そのような部分のソースコードをより共通化することができるだろう。