Compartir a través de


Subclases genéricas de NSObject en Xamarin.iOS

Uso de genéricos con NSObjects

Es posible usar genéricos en las subclases de NSObject, por ejemplo, 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);
    }
}

Dado que los objetos de la subclase NSObject se registran con el entorno de ejecución de Objective-C, existen algunas limitaciones en cuanto a lo que se puede hacer con las subclases genéricas de tipos NSObject.

Consideraciones para las subclases genéricas de NSObject

En este documento se detallan las limitaciones de la compatibilidad limitada de las subclases genéricas de NSObjects.

Argumentos de tipo genérico en signaturas de miembro

Todos los argumentos de tipo genérico de una signatura de miembro expuesta a Objective-C deben tener una restricción NSObject.

Correcto:

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

Motivo: el parámetro de tipo genérico es un objectoNSObject, por lo que la signatura de selector para myMethod: se puede exponer de forma segura a Objective-C (siempre será NSObject o una subclase suya).

Incorrecto:

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

Motivo: no es posible crear una firma Objective-C para los miembros exportados a los que Objective-C código puede llamar, ya que la firma variaría en función del tipo exacto del tipo genérico T.

Correcto:

class Generic<T> : NSObject
{
    T storage;

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

Motivo: es posible tener argumentos de tipo genérico sin restricciones siempre que no formen parte de la signatura del miembro exportado.

Correcto:

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

Motivo: el parámetro T del MyMethodexportado de Objective-C está restringido a que sea un objeto NSObject, el tipo sin restricciones U no forma parte de la signatura.

Incorrecto:

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: registrar aún no admite este escenario. Para obtener más información, consulte estos problemas de GitHub.

Creaciones de instancias de tipos genéricos de Objective-C

La creación de una instancia desde Objective-C no está permitida. Esto suele ocurrir cuando se usa un tipo administrado en un archivo xib o un guion gráfico.

Considere esta definición de clase, que expone un constructor que toma un elemento IntPtr (la forma de Xamarin.iOS de construir un objeto C# a partir de una instancia nativa de Objective-C):

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

Aunque la construcción anterior es correcta, en el entorno de ejecución, se producirá una excepción si Objective-C intenta crear una instancia.

Esto sucede porque Objective-C no tiene ningún concepto de tipos genéricos y no puede especificar el tipo genérico exacto que se va a crear.

Este problema se puede solucionar creando una subclase especializada del tipo genérico. Por ejemplo:

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

class GenericUIView : Generic<UIView>
{
}

Ahora, ya no hay ambigüedad, la clase GenericUIView se puede usar en archivos xib o guiones gráficos.

No hay compatibilidad para métodos genéricos

No se permiten los métodos genéricos.

El código siguiente no se compilará:

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

Motivo: no se permite porque Xamarin.iOS no sabe qué tipo usar para el argumento de tipo T cuando el método se invoca desde Objective-C.

Una alternativa es crear un método especializado y exportarlo en su lugar:

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

No se permiten miembros estáticos exportados

No se pueden exponer miembros estáticos a Objective-C si se hospedan en una subclase genérica de NSObject.

Ejemplo de un escenario no admitido:

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

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

Motivo: al igual que los métodos genéricos, el entorno de ejecución de Xamarin.iOS debe ser capaz de saber qué tipo usar para el argumento de tipo genérico T.

En el caso de los miembros de instancia, se usa la propia instancia (dado que nunca habrá una instancia Generic<T>, siempre será Generic<SomeSpecificClass>), pero para los miembros estáticos esta información no está presente.

Tenga en cuenta que esto se aplica incluso si el miembro en cuestión no utiliza el argumento de tipo T de ninguna manera.

En este caso, la alternativa es crear una subclase 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; }
}

Rendimiento

El elemento estático registrar no puede resolver un miembro exportado en un tipo genérico en tiempo de compilación como hace normalmente, debe buscarse en tiempo de ejecución. Esto significa que invocar este método de Objective-C es ligeramente más lento que invocar miembros de clases no genéricas.