แชร์ผ่าน


Generic subclasses of NSObject in Xamarin.iOS

Using generics with NSObjects

It's possible to use generics in subclasses of NSObject, for example 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);
    }
}

Since objects that subclass NSObject are registered with the Objective-C runtime there are some limitations as to what is possible with generic subclasses of NSObject types.

Considerations for generic subclasses of NSObject

This document details the limitations in the limited support for generic subclasses of NSObjects.

Generic Type Arguments in Member Signatures

All generic type arguments in a member signature exposed to Objective-C must have an NSObject constraint.

Good:

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

Reason: The generic type parameter is an NSObject, so the selector signature for myMethod: can be safely exposed to Objective-C (it will always be NSObject or a subclass of it).

Bad:

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

Reason: it is not possible to create an Objective-C signature for the exported members that Objective-C code can call, since the signature would differ depending on the exact type of the generic type T.

Good:

class Generic<T> : NSObject
{
    T storage;

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

Reason: it is possible to have unconstrained generic type arguments as long as they do not take part of the exported member signature.

Good:

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

Reason: the T parameter in the Objective-C exported MyMethod is constrained to be an NSObject, the unconstrained type U is not part of the signature.

Bad:

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());

}

Reason: the registrar doesn't support this scenario yet. For more information, see this GitHub issues.

Instantiations of Generic Types from Objective-C

Instantiation of generic types from Objective-C is not allowed. This typically occurs when a managed type is used in a xib or a storyboard.

Consider this class definition, which exposes a constructor that takes an IntPtr (the Xamarin.iOS way of constructing a C# object from a native Objective-C instance):

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

While the above construct is fine, at runtime, this will throw an exception at if Objective-C tries to create an instance of it.

This is happens because Objective-C has no concept of generic types, and it cannot specify the exact generic type to create.

This problem can be worked around by creating a specialized subclass of the generic type. For example:

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

class GenericUIView : Generic<UIView>
{
}

Now there is no ambiguity anymore, the class GenericUIView can be used in xibs or storyboards.

No support for generic methods

Generic methods are not allowed.

The following code will not compile:

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

Reason: This is not allowed because Xamarin.iOS does not know which type to use for the type argument T when the method is invoked from Objective-C .

An alternative is to create a specialized method and export that instead:

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

No exported static members allowed

You can not expose a static members to Objective-C if it is hosted inside a generic subclass of NSObject.

Example of an unsupported scenario:

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

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

Reason: Just like generic methods, the Xamarin.iOS runtime needs to be able to know what type to use for the generic type argument T.

For instance members the instance itself is used (since there will never be an instance Generic<T>, it will always be Generic<SomeSpecificClass>), but for static members this information is not present.

Note that this applies even if the member in question does not use the type argument T in any way.

The alternative in this case is to create a specialized subclass:

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; }
}

Performance

The static registrar can't resolve an exported member in a generic type at build time as it usually does, it has to be looked up at runtime. This means that invoking such a method from Objective-C is slightly slower than invoking members from non-generic classes.