Generische Unterklassen von NSObject in Xamarin.iOS

Verwenden von Generics mit NSObjects

Es ist möglich, Generics in Unterklassen von NSObject zu verwenden, z. B. 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);
    }
}

Da Objekte dieser Unterklasse NSObject mit der Objective-C-Runtime registriert werden, gibt es einige Einschränkungen hinsichtlich der Möglichkeiten, die für generische Unterklassen von NSObject-Typen gegeben sind.

Überlegungen zu generischen Unterklassen von NSObject

In diesem Dokument werden die Einschränkungen der eingeschränkten Unterstützung für generische Unterklassen von NSObjects ausführlich erläutert.

Generische Typargumente in Membersignaturen

Alle generischen Typargumente in einer Membersignatur, die für Objective-C verfügbar gemacht ist, müssen über eine NSObject-Einschränkung verfügen.

Gut:

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

Grund: Der generische Typparameter ist ein NSObject, sodass die Selektorsignatur für myMethod: sicher für Objective-C verfügbar gemacht werden kann (sie ist immer NSObject oder eine Unterklasse davon).

Schlecht:

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

Grund: Es ist nicht möglich, eine Objective-C Signatur für die exportierten Member zu erstellen, die Objective-C code aufrufen kann, da die Signatur je nach dem genauen Typ des generischen Typs Tunterschiedlich wäre.

Gut:

class Generic<T> : NSObject
{
    T storage;

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

Grund: Es ist möglich, uneingeschränkte generische Typargumente zu verwenden, solange sie nicht Teil der exportierten Membersignatur sind.

Gut:

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

Grund: Der T-Parameter in der von Objective-C exportierten MyMethod ist dahingehend eingeschränkt, dass er nur ein NSObject sein kann, während der uneingeschränkte Typ U nicht Teil der Signatur ist.

Schlecht:

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

}

Grund: Dieses registrar Szenario wird noch nicht unterstützt. Weitere Informationen finden Sie in diesen GitHub-Problemen.

Instanziierungen generischer Typen aus Objective-C

Die Instanziierung generischer Typen aus Objective-C ist nicht zulässig. Dies tritt normalerweise auf, wenn ein verwalteter Typ in einer XIB oder einem Storyboard verwendet wird.

Beachten Sie diese Klassendefinition, die einen Konstruktor verfügbar macht, der einen IntPtr akzeptiert (die Art von Xamarin.iOS zur Konstruktion eines C#-Objekts aus einer nativen Objective-C-Instanz):

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

Das obige Konstrukt ist zwar in Ordnung, löst zur Laufzeit aber eine Ausnahme aus, wenn Objective-C versucht, eine Instanz davon zu erstellen.

Dies liegt daran, dass Objective-C kein Konzept generischer Typen besitzt und nicht den genauen generischen Typ angeben kann, der erstellt werden soll.

Sie können dieses Problem umgehen, indem Sie eine spezialisierte Unterklasse des generischen Typs erstellen. Beispiel:

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

class GenericUIView : Generic<UIView>
{
}

Nun liegt keine Mehrdeutigkeit mehr vor, und die Klasse GenericUIView kann in XIBs oder Storyboards verwendet werden kann.

Keine Unterstützung für generische Methoden

Generische Methoden sind nicht zulässig.

Der folgende Code lässt sich nicht kompilieren:

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

Grund: Dies ist nicht zulässig, weil Xamarin.iOS nicht weiß, welcher Typ für das Typargument T verwendet werden soll, wenn die Methode aus Objective-C aufgerufen wird.

Eine Alternative besteht darin, eine spezialisierte Methode zu erstellen und Folgendes stattdessen zu exportieren:

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

Keine exportierten statischen Member zulässig

Sie können keine statischen Member für Objective-C verfügbar machen, wenn sie in einer generischen Unterklasse von NSObject gehostet werden.

Beispiel für ein nicht unterstütztes Szenario:

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

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

Grund: Wie bei generischen Methoden muss die Xamarin.iOS-Runtime bestimmen können, welcher Typ für das generische Typargument Tverwendet werden soll.

Bei Instanzmembern wird die Instanz selbst verwendet (da nie eine Instanz Generic<T> vorhanden sein wird, es wird immer eine Generic<SomeSpecificClass> sein), aber für statische Member sind diese Informationen nicht vorhanden.

Beachten Sie, dass dies auch dann gilt, wenn der betreffende Member das Typargument T in keiner Weise verwendet.

Die Alternative besteht in diesem Fall darin, eine spezialisierte Unterklasse zu erstellen:

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

Leistung

Der statische registrar kann einen exportierten Member zur Buildzeit nicht wie gewöhnlich in einen generischen Typ auflösen, da er zur Laufzeit nachgeschlagen werden muss. Dies bedeutet, dass das Aufrufen einer solchen Methode Objective-C etwas langsamer ist als das Aufrufen von Membern aus nicht generischen Klassen.