Xamarin.iOS での NSObject のジェネリック サブクラス

NSObject でのジェネリックの使用

NSObject のサブクラスでジェネリックを使用できます (UIView など)。

class Foo<T> : UIView {
    public Foo (CGRect x) : base (x) {}
    public override void Draw (CoreGraphics.CGRect rect)
    {
        Console.WriteLine ("T: {0}. Type: {1}", typeof (T), GetType ().Name);
    }
}

NSObject をサブクラス化するオブジェクトは Objective-C のランタイムに登録されるため、NSObject 型のジェネリック サブクラスでできることにはいくつかの制限があります。

NSObject のジェネリック サブクラスに関する考慮事項

このドキュメントでは、NSObjects のジェネリック サブクラスの制限付きサポートの制限事項について詳しく説明します。

メンバー シグネチャでのジェネリック型引数

Objective-C に公開されているメンバー シグネチャ内のすべてのジェネリック型引数には、NSObject 制約が必要です。

適切:

class Generic<T> : NSObject where T: NSObject
{
    [Export ("myMethod:")]
    public void MyMethod (T value)
    {
    }
}

理由: ジェネリック型パラメーターは NSObject であるため、myMethod: のセレクター シグネチャを Objective-C に安全に公開できます (常に NSObject またはそのサブクラスになります)。

不適切:

class Generic<T> : NSObject
{
    [Export ("myMethod:")]
    public void MyMethod (T value)
    {
    }
}

理由: ジェネリック型 T の正確な型によってシグネチャが異なるため、Objective-C のコードで呼び出すことができるエクスポートされたメンバーに対して、Objective-C のシグネチャを作成できません。

適切:

class Generic<T> : NSObject
{
    T storage;

    [Export ("myMethod:")]
    public void MyMethod (NSObject value)
    {
    }
}

理由: エクスポートされるメンバー シグネチャの一部でない限り、制約のないジェネリック型引数を使用できます。

適切:

class Generic<T, U> : NSObject where T: NSObject
{
    [Export ("myMethod:")]
    public void MyMethod (T value)
    {
        Console.WriteLine (typeof (U));
    }
}

理由: Objective-C のエクスポートされた MyMethod における T パラメーターは NSObject になるように制約されているため、制約のない型 U はシグネチャの一部ではありません。

不適切:

class Generic<T> : NSObject
{
    public T Storage { get; }

    public Generic(T value)
    {
        Storage = value;
    }
}

[Register("Foo")]
class Foo: NSView
{
    [Export("Test")]
    public Generic<int> Test { get; set; } = new Generic<int>(22);

    [Export("Test1")]
    public Generic<object> Test1 { get; set; } = new Generic<object>(new object());

    [Export("Test2")]
    public Generic<NSObject> Test2 { get; set; } = new Generic<NSObject>(new NSObject());

}

理由: registrar はまだこのシナリオをサポートしていません。 詳細については、こちらの GitHub の issue を参照してください。

Objective-C からのジェネリック型のインスタンス化

Objective-C からのジェネリック型のインスタンス化は許可されていません。 これは通常、xib またはストーリーボードでマネージド型が使用されている場合に発生します。

このクラスの定義について考えてみましょう。これは IntPtr (Xamarin.iOS において、ネイティブ Objective-C インスタンスから C# オブジェクトを構築する方法) を受け取るコンストラクターを公開します。

class Generic<T> : NSObject where T : NSObject
{
    public Generic () {}
    public Generic (IntPtr ptr) : base (ptr) {}
}

上記のコンストラクトは問題ありませんが、実行時には、Objective-C でそのインスタンスを作成しようとすると、例外がスローされます。

これは、Objective-C にはジェネリック型の概念がなく、作成する厳密なジェネリック型を指定できないために発生します。

この問題は、ジェネリック型の特殊なサブクラスを作成することによって回避できます。 次に例を示します。

class Generic<T> : NSObject where T : NSObject
{
    public Generic () {}
    public Generic (IntPtr ptr) : base (ptr) {}
}

class GenericUIView : Generic<UIView>
{
}

これで、あいまいさがなくなったため、xib またはストーリーボードで GenericUIView クラスを使用できます。

ジェネリック メソッドはサポートされない

ジェネリック メソッドは許可されません。

次のコードはコンパイルされません。

class MyClass : NSObject
{
    [Export ("myMethod")]
    public void MyMethod<T> (T argument)
    {
    }
}

理由: メソッドが Objective-C から呼び出されるとき、Xamarin.iOS では型引数 T に対して使用する型が認識されないため、これは許可されません。

代わりに、特殊なメソッドを作成してエクスポートする必要があります。

class MyClass : NSObject
{
    [Export ("myMethod")]
    public void MyUIViewMethod (UIView argument)
    {
        MyMethod<UIView> (argument);
    }
    public void MyMethod<T> (T argument)
    {
    }
}

エクスポートされた静的メンバーは許可されない

静的メンバーが NSObject のジェネリック サブクラスの内部でホストされている場合、そのメンバーを Objective-C に公開することはできません。

サポートされていないシナリオの例:

class Generic<T> : NSObject where T : NSObject
{
    [Export ("myMethod:")]
    public static void MyMethod ()
    {
    }

    [Export ("myProperty")]
    public static T MyProperty { get; set; }
}

理由: ジェネリック メソッドと同様に、Xamarin.iOS のランタイムでは、ジェネリック型引数 T に対して使用する型を認識できる必要があります。

インスタンス メンバーの場合はインスタンス自体が使用されますが (インスタンス Generic<T> はないため、常に Generic<SomeSpecificClass> になります)、静的メンバーの場合はこの情報は存在しません。

これは、問題のメンバーで型引数 T が使用されていない場合でも適用されることに注意してください。

この場合の代替手段は、特殊なサブクラスを作成することです。

class GenericUIView : Generic<UIView>
{
    [Export ("myUIViewMethod")]
    public static void MyUIViewMethod ()
    {
        MyMethod ();
    }

    [Export ("myProperty")]
    public static UIView MyUIViewProperty {
        get { return MyProperty; }
        set { MyProperty = value; }
    }
}

class Generic<T> : NSObject where T : NSObject
{
    public static void MyMethod () {}
    public static T MyProperty { get; set; }
}

パフォーマンス

静的 registrar では、通常のようにビルド時にジェネリック型のエクスポートされたメンバーを解決することはできず、実行時に検索する必要があります。 これは、そのようなメソッドを Objective-C から呼び出すと、非ジェネリック クラスのメンバーを呼び出すより少し遅くなることを意味します。