Subclasses genéricas de NSObject no Xamarin.iOS
Usando genéricos com NSObjects
É possível usar genéricos em subclasses de NSObject
, por exemplo , 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);
}
}
Como os objetos dessa subclasse NSObject
são registrados com o Objective-C tempo de execução, há algumas limitações quanto ao que é possível com subclasses genéricas de NSObject
tipos.
Considerações para subclasses genéricas de NSObject
Este documento detalha as limitações no suporte limitado para subclasses genéricas do NSObjects
.
Argumentos de tipo genérico em assinaturas de membro
Todos os argumentos de tipo genérico em uma assinatura de membro exposta a Objective-C devem ter uma NSObject
restrição.
Bom:
class Generic<T> : NSObject where T: NSObject
{
[Export ("myMethod:")]
public void MyMethod (T value)
{
}
}
Motivo: O parâmetro de tipo genérico é um NSObject
, portanto, a assinatura do seletor para myMethod:
pode ser exposta Objective-C com segurança (sempre será NSObject
ou uma subclasse dele).
Ruim:
class Generic<T> : NSObject
{
[Export ("myMethod:")]
public void MyMethod (T value)
{
}
}
Motivo: não é possível criar uma assinatura para os membros exportados que o código pode chamar, uma Objective-C vez que Objective-C a assinatura seria diferente dependendo do tipo exato do tipo T
genérico.
Bom:
class Generic<T> : NSObject
{
T storage;
[Export ("myMethod:")]
public void MyMethod (NSObject value)
{
}
}
Motivo: é possível ter argumentos de tipo genérico irrestritos, desde que eles não façam parte da assinatura do membro exportado.
Bom:
class Generic<T, U> : NSObject where T: NSObject
{
[Export ("myMethod:")]
public void MyMethod (T value)
{
Console.WriteLine (typeof (U));
}
}
Motivo: o Objective-CT
parâmetro na exportação MyMethod
é restrito a ser um NSObject
, o tipo U
irrestrito não faz parte da assinatura.
Ruim:
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());
}
Motivo: o registrar cenário ainda não suporta. Para obter mais informações, consulte Problemas do GitHub.
Instanciações de tipos genéricos de Objective-C
A instanciação de tipos genéricos de Objective-C não é permitida. Isso normalmente ocorre quando um tipo gerenciado é usado em um xib ou um storyboard.
Considere esta definição de classe, que expõe um construtor que usa um (a maneira Xamarin.iOS de construir um IntPtr
objeto C# a partir de uma instância nativa Objective-C ):
class Generic<T> : NSObject where T : NSObject
{
public Generic () {}
public Generic (IntPtr ptr) : base (ptr) {}
}
Embora a construção acima seja boa, em tempo de execução, isso lançará uma exceção em se Objective-C tentar criar uma instância dela.
Isso acontece porque Objective-C não tem conceito de tipos genéricos, e não pode especificar o tipo genérico exato a ser criado.
Esse problema pode ser resolvido criando uma subclasse especializada do tipo genérico. Por exemplo:
class Generic<T> : NSObject where T : NSObject
{
public Generic () {}
public Generic (IntPtr ptr) : base (ptr) {}
}
class GenericUIView : Generic<UIView>
{
}
Agora não há mais ambiguidade, a classe GenericUIView
pode ser usada em xibs ou storyboards.
Não há suporte para métodos genéricos
Métodos genéricos não são permitidos.
O código a seguir não será compilado:
class MyClass : NSObject
{
[Export ("myMethod")]
public void MyMethod<T> (T argument)
{
}
}
Motivo: Isso não é permitido porque o Xamarin.iOS não sabe qual tipo usar para o argumento T
type quando o método é chamado de Objective-C .
Uma alternativa é criar um método especializado e exportar que:
class MyClass : NSObject
{
[Export ("myMethod")]
public void MyUIViewMethod (UIView argument)
{
MyMethod<UIView> (argument);
}
public void MyMethod<T> (T argument)
{
}
}
Nenhum membro estático exportado permitido
Não é possível expor um membro estático para Objective-C se ele estiver hospedado dentro de uma subclasse genérica de NSObject
.
Exemplo de um cenário sem suporte:
class Generic<T> : NSObject where T : NSObject
{
[Export ("myMethod:")]
public static void MyMethod ()
{
}
[Export ("myProperty")]
public static T MyProperty { get; set; }
}
Motivo: Assim como os métodos genéricos, o tempo de execução do Xamarin.iOS precisa ser capaz de saber que tipo usar para o argumento T
de tipo genérico.
Por exemplo, membros a instância em si é usada (já que nunca haverá uma instância Generic<T>
, ela sempre será Generic<SomeSpecificClass>
), mas para membros estáticos essa informação não está presente.
Observe que isso se aplica mesmo que o membro em questão não use o argumento T
type de forma alguma.
A alternativa, nesse caso, é criar uma subclasse especializada:
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; }
}
Desempenho
O estático registrar não pode resolver um membro exportado em um tipo genérico em tempo de compilação como normalmente faz, ele tem que ser examinado em tempo de execução. Isso significa que invocar tal método de Objective-C é um pouco mais lento do que invocar membros de classes não genéricas.