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
,因此可以向 Objective-C 安全地公开 myMethod:
的选择器签名(它始终是 NSObject
或其子类)。
错误:
class Generic<T> : NSObject
{
[Export ("myMethod:")]
public void MyMethod (T value)
{
}
}
原因:无法为可供 Objective-C 代码调用的已导出成员创建 Objective-C 签名,因为签名因泛型类型 T
的确切类型而异。
规范:
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 问题。
Objective-C 中的泛型类型实例化
不允许使用 Objective-C 中的泛型类型实例化。 当在 xib 或情节提要中使用托管类型时,通常会发生这种情况。
不妨使用下面的类定义,它公开需要使用 IntPtr
的构造函数(通过本机 Objective-C 实例构造 C# 对象的 Xamarin.iOS 方法):
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)
{
}
}
不允许使用已导出静态成员
不能向 Objective-C 公开 NSObject
的泛型子类中托管的静态成员。
不受支持的方案示例:
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 调用此类方法比通过非泛型类调用成员稍微慢一些。