Sottoclassi generiche di NSObject in Xamarin.iOS

Uso di generics con NSObjects

È possibile usare generics nelle sottoclassi di NSObject, ad esempio 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);
    }
}

Poiché gli oggetti che sottoclasse NSObject vengono registrati con il Objective-C runtime, esistono alcune limitazioni per quanto possibile con sottoclassi generiche di NSObject tipi.

Considerazioni sulle sottoclassi generiche di NSObject

Questo documento illustra in dettaglio le limitazioni del supporto limitato per le sottoclassi generiche di NSObjects.

Argomenti di tipo generico nelle firme dei membri

Tutti gli argomenti di tipo generico in una firma membro esposta a Objective-C devono avere un NSObject vincolo.

Buono:

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

Motivo: il parametro di tipo generico è un NSObjectoggetto , quindi la firma del selettore per myMethod: può essere esposta in modo sicuro a Objective-C (sarà sempre NSObject o una sottoclasse di esso).

Non valido:

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

Motivo: non è possibile creare una Objective-C firma per i membri esportati che Objective-C il codice può chiamare, perché la firma differisce a seconda del tipo esatto del tipo Tgenerico .

Buono:

class Generic<T> : NSObject
{
    T storage;

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

Motivo: è possibile avere argomenti di tipo generico non vincolati purché non facciano parte della firma del membro esportato.

Buono:

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

Motivo: il T parametro nell'oggetto Objective-C esportato è vincolato MyMethod a essere , NSObjectil tipo U non vincolato non fa parte della firma.

Non valido:

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: non registrar supporta ancora questo scenario. Per altre informazioni, vedere i problemi di GitHub.

Creazione di istanze di tipi generici da Objective-C

La creazione di istanze di tipi generici da Objective-C non è consentita. Ciò si verifica in genere quando un tipo gestito viene usato in uno storyboard o xib.

Si consideri questa definizione di classe, che espone un costruttore che accetta un IntPtr oggetto (il modo Xamarin.iOS di costruire un oggetto C# da un'istanza nativa Objective-C ):

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

Anche se il costrutto precedente è corretto, in fase di esecuzione verrà generata un'eccezione se Objective-C tenta di crearne un'istanza.

Ciò avviene perché Objective-C non ha alcun concetto di tipi generici e non può specificare il tipo generico esatto da creare.

Questo problema può essere risolto creando una sottoclasse specializzata del tipo generico. Ad esempio:

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

class GenericUIView : Generic<UIView>
{
}

Ora non c'è più ambiguità, la classe GenericUIView può essere usata in xibs o storyboard.

Nessun supporto per i metodi generici

I metodi generici non sono consentiti.

Il codice seguente non verrà compilato:

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

Motivo: questo non è consentito perché Xamarin.iOS non conosce il tipo da usare per l'argomento T di tipo quando il metodo viene richiamato da Objective-C .

Un'alternativa consiste nel creare un metodo specializzato ed esportare che:

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

Nessun membro statico esportato consentito

Non è possibile esporre membri statici a Objective-C se è ospitato all'interno di una sottoclasse generica di NSObject.

Esempio di uno scenario non supportato:

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

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

Motivo: proprio come i metodi generici, il runtime di Xamarin.iOS deve essere in grado di conoscere il tipo da usare per l'argomento di tipo generico T.

Per i membri dell'istanza viene usata l'istanza stessa ( poiché non sarà mai presente un'istanza Generic<T>, sarà sempre Generic<SomeSpecificClass>), ma per i membri statici queste informazioni non sono presenti.

Si noti che questo vale anche se il membro in questione non usa l'argomento T di tipo in alcun modo.

L'alternativa in questo caso consiste nel creare una sottoclasse specializzata:

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

Prestazioni

L'oggetto statico registrar non può risolvere un membro esportato in un tipo generico in fase di compilazione come in genere, deve essere esaminato in fase di esecuzione. Ciò significa che richiamare tale metodo da Objective-C è leggermente più lento rispetto al richiamo di membri da classi non generiche.