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.