Sous-classes génériques de NSObject dans Xamarin.iOS

Utilisation de génériques avec NSObjects

Il est possible d’utiliser des génériques dans des sous-classes de NSObject, par exemple 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);
    }
}

Étant donné que les objets qui sous-classe NSObject sont inscrits auprès du Objective-C runtime, il existe certaines limitations quant à ce qui est possible avec les sous-classes génériques de NSObject types.

Considérations relatives aux sous-classes génériques de NSObject

Ce document détaille les limitations de la prise en charge limitée des sous-classes génériques de NSObjects.

Arguments de type générique dans les signatures de membre

Tous les arguments de type générique d’une signature membre exposées Objective-C doivent avoir une NSObject contrainte.

Bon :

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

Motif : le paramètre de type générique est un NSObject, de sorte que la signature du sélecteur peut myMethod: être exposée Objective-C en toute sécurité (elle sera NSObject toujours ou une sous-classe de celle-ci).

Mauvais :

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

Motif : il n’est pas possible de créer une Objective-C signature pour les membres exportés que Objective-C le code peut appeler, car la signature diffère selon le type exact du type Tgénérique.

Bon :

class Generic<T> : NSObject
{
    T storage;

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

Motif : il est possible d’avoir des arguments de type générique non contraints tant qu’ils ne prennent pas partie de la signature membre exportée.

Bon :

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

Motif : le T paramètre dans l’exportation Objective-CMyMethod est contraint d’être un NSObject, le type U sans contrainte ne fait pas partie de la signature.

Mauvais :

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

}

Raison : le registrar scénario ne prend pas encore en charge ce scénario. Pour plus d’informations, consultez ces problèmes GitHub.

Instanciations de types génériques à partir de Objective-C

L’instanciation des types génériques à partir de Objective-C n’est pas autorisée. Cela se produit généralement lorsqu’un type managé est utilisé dans un xib ou un storyboard.

Considérez cette définition de classe, qui expose un constructeur qui prend un IntPtr (la façon Xamarin.iOS de construire un objet C# à partir d’une instance native Objective-C ) :

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

Bien que la construction ci-dessus soit correcte, au moment de l’exécution, cela lève une exception si Objective-C vous tentez de créer une instance de celle-ci.

Cela se produit parce qu’il Objective-C n’existe aucun concept de types génériques et qu’il ne peut pas spécifier le type générique exact à créer.

Ce problème peut être travaillé en créant une sous-classe spécialisée du type générique. Par exemple :

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

class GenericUIView : Generic<UIView>
{
}

Maintenant, il n’y a plus d’ambiguïté, la classe GenericUIView peut être utilisée dans des xibs ou des storyboards.

Aucune prise en charge des méthodes génériques

Les méthodes génériques ne sont pas autorisées.

Le code suivant ne sera pas compilé :

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

Raison : Ceci n’est pas autorisé, car Xamarin.iOS ne sait pas quel type utiliser pour l’argument T de type lorsque la méthode est appelée à partir de Objective-C .

Une alternative consiste à créer une méthode spécialisée et à exporter à la place :

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

Aucun membre statique exporté autorisé

Vous ne pouvez pas exposer de membres statiques s’il Objective-C est hébergé à l’intérieur d’une sous-classe générique de NSObject.

Exemple de scénario non pris en charge :

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

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

Raison : tout comme les méthodes génériques, le runtime Xamarin.iOS doit être en mesure de savoir quel type utiliser pour l’argument Tde type générique.

Par exemple, les membres de l’instance elle-même sont utilisés (car il n’y aura jamais d’instance Generic<T>, il sera toujours Generic<SomeSpecificClass>), mais pour les membres statiques, ces informations ne sont pas présentes.

Notez que cela s’applique même si le membre en question n’utilise pas l’argument T de type de quelque manière que ce soit.

L’alternative dans ce cas consiste à créer une sous-classe spécialisée :

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

Performances

Le statique registrar ne peut pas résoudre un membre exporté dans un type générique au moment de la génération, car il doit généralement être examiné au moment de l’exécution. Cela signifie que l’appel d’une telle méthode Objective-C est légèrement plus lent que l’appel de membres à partir de classes non génériques.