プログラミング C# - 翔ソフトウェア (Sho's)

C#/.NET/ソフトウェア開発など

ポータブル クラス ライブラリに関する検証

Windows 8 Store apps Advent Calendar の 20日目のエントリー。

以前、「Windows Store アプリと Windows Phone アプリ、Silverlight アプリ、WPF アプリでソースコードを共通化する方法に関する記事」と云う記事でポータブル クラス ライブラリに関して少しだけご紹介した。

今回は、ポータブル クラス ライブラリについて、更に調べてみよう。

■ アプリケーションの種類と .NET Framework の違い

Windows ストア アプリで参照している .NET は、他のアプリケーションで参照しているものと少し異なる。

その様子を先ず確認してみよう。

現時点で最新の環境で何種類かのアプリケーションをデフォルトで追加してみて、参照している .NET を見てみる。

・コンソール アプリケーションとクラス ライブラリの場合 (.NET Framework 4.5)
コンソール アプリケーションとクラス ライブラリで参照している .NET
コンソール アプリケーションとクラス ライブラリで参照している .NET
WPF の場合 (.NET Framework 4.5)
WPF アプリケーションで参照している .NET
WPF アプリケーションで参照している .NET
Silverlight の場合 (Silverlight 5)
Silverlight アプリケーションと Silverlight クラス ライブラリで参照している .NET
Silverlight アプリケーションと Silverlight クラス ライブラリで参照している .NET
・Windows Phone の場合 (Windows Phone OS 8.0)
Windows Phone アプリと Windows Phone クラス ライブラリで参照している .NET
Windows Phone アプリと Windows Phone クラス ライブラリで参照している .NET

「.NET for Windows Phone」となっている。

・Windows ストア アプリの場合
Windows ストア アプリとWindows ストア クラス ライブラリで参照している .NET
Windows ストア アプリとWindows ストア クラス ライブラリで参照している .NET

こちらでは、「.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 のデバッガーで値を確認してみると、次のようになった。

コンソール アプリケーションの場合の実行結果 1
コンソール アプリケーションの場合の実行結果 1
コンソール アプリケーションの場合の実行結果 2
コンソール アプリケーションの場合の実行結果 2
コンソール アプリケーションの場合の実行結果 3
コンソール アプリケーションの場合の実行結果 3
WPFSilverlight の場合

同様のことを、WPFSilverlight の場合で試してみると次のようになる。

先ず 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
このポータブル クラス ライブラリが参照している .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 のコア部分を使ったソースコードでも共通のものが使えないことがあることが判った。

ポータブル クラス ライブラリを使うことで、そのような部分のソースコードをより共通化することができるだろう。