Angenommen, ich habe ein Protokoll:
public protocol Printable {
typealias T
func Print(val:T)
}
Und hier ist die Implementierung
class Printer<T> : Printable {
func Print(val: T) {
println(val)
}
}
Meine Erwartung war, dass ich in der Lage sein muss, Printable
Variablen zum Drucken von Werten wie diesen zu verwenden:
let p:Printable = Printer<Int>()
p.Print(67)
Der Compiler beschwert sich über diesen Fehler:
"Das Protokoll 'Druckbar' kann nur als allgemeine Einschränkung verwendet werden, da es Selbst- oder zugehörige Typanforderungen hat."
Mache ich etwas falsch ? Wie auch immer, um das zu beheben?
**EDIT :** Adding similar code that works in C#
public interface IPrintable<T>
{
void Print(T val);
}
public class Printer<T> : IPrintable<T>
{
public void Print(T val)
{
Console.WriteLine(val);
}
}
//.... inside Main
.....
IPrintable<int> p = new Printer<int>();
p.Print(67)
EDIT 2: Beispiel aus der Praxis für das, was ich will. Beachten Sie, dass dies nicht kompiliert wird, sondern das darstellt, was ich erreichen möchte.
protocol Printable
{
func Print()
}
protocol CollectionType<T where T:Printable> : SequenceType
{
.....
/// here goes implementation
.....
}
public class Collection<T where T:Printable> : CollectionType<T>
{
......
}
let col:CollectionType<Int> = SomeFunctiionThatReturnsIntCollection()
for item in col {
item.Print()
}
Antworten:
Wie Thomas betont, können Sie Ihre Variable deklarieren, indem Sie überhaupt keinen Typ angeben (oder Sie können ihn explizit als Typ
Printer<Int>
angeben. Hier finden Sie eine Erklärung, warum Sie keinen Typ desPrintable
Protokolls haben können.Sie können Protokolle mit zugeordneten Typen nicht wie reguläre Protokolle behandeln und sie als eigenständige Variablentypen deklarieren. Betrachten Sie dieses Szenario, um darüber nachzudenken, warum. Angenommen, Sie haben ein Protokoll zum Speichern und Abrufen eines beliebigen Typs deklariert:
OK, soweit so gut.
Der Hauptgrund dafür, dass ein Variablentyp ein Protokoll ist, das ein Typ implementiert, und nicht der tatsächliche Typ, besteht darin, dass Sie derselben Variablen verschiedene Objekttypen zuweisen können, die alle diesem Protokoll entsprechen, und polymorph werden Verhalten zur Laufzeit abhängig davon, was das Objekt tatsächlich ist.
Sie können dies jedoch nicht tun, wenn dem Protokoll ein Typ zugeordnet ist. Wie würde der folgende Code in der Praxis funktionieren?
Was wäre im obigen Code die Art von
x
? EinInt
? Oder einString
? In Swift müssen alle Typen zur Kompilierungszeit festgelegt werden. Eine Funktion kann nicht dynamisch von der Rückgabe eines Typs zu einem anderen wechseln, basierend auf zur Laufzeit bestimmten Faktoren.Stattdessen können Sie nur
StoredType
als allgemeine Einschränkung verwenden. Angenommen, Sie möchten einen gespeicherten Typ ausdrucken. Sie könnten eine Funktion wie diese schreiben:Dies ist in Ordnung, da es beim Kompilieren so ist, als würde der Compiler zwei Versionen von ausschreiben
printStoredValue
: eine fürInt
s und eine fürString
s. Innerhalb dieser beiden Versionenx
ist bekannt, dass es sich um einen bestimmten Typ handelt.quelle
p
Variablen Drucker unterschiedlichen Typs zuweisen und dann ungültige Typen übergeben würdenprint
? Laufzeitausnahme?var someStorer: StoringType<Int>
odervar someStorer: StoringType<String>
ausführen und das von Ihnen skizzierte Problem lösen.Es gibt eine weitere Lösung, die in dieser Frage nicht erwähnt wurde, nämlich die Verwendung einer Technik namens Typlöschung . Um eine abstrakte Schnittstelle für ein generisches Protokoll zu erhalten, erstellen Sie eine Klasse oder Struktur, die ein Objekt oder eine Struktur umschließt, die dem Protokoll entspricht. Die Wrapper-Klasse, normalerweise 'Any {Protokollname}' genannt, entspricht selbst dem Protokoll und implementiert seine Funktionen, indem alle Aufrufe an das interne Objekt weitergeleitet werden. Probieren Sie das folgende Beispiel auf einem Spielplatz aus:
Der Typ von
printer
ist bekanntAnyPrinter<Int>
und kann verwendet werden, um jede mögliche Implementierung des Druckerprotokolls zu abstrahieren. Obwohl AnyPrinter technisch nicht abstrakt ist, ist seine Implementierung nur ein Durchfall zu einem echten Implementierungstyp und kann verwendet werden, um Implementierungstypen von den Typen zu entkoppeln, die sie verwenden.Zu beachten ist, dass die
AnyPrinter
Basisinstanz nicht explizit beibehalten werden muss. In der Tat können wir nicht, da wir nicht erklären könnenAnyPrinter
, einePrinter<T>
Immobilie zu haben . Stattdessen erhalten wir einen Funktionszeiger_print
auf dieprint
Funktion der Basis . Aufrufbase.print
ohne es den Aufruf gibt eine Funktion , wo Base als Selbst Variable curried wird und auf diese Weise für zukünftige Anrufungen beibehalten.Eine andere Sache, die Sie beachten sollten, ist, dass diese Lösung im Wesentlichen eine weitere Schicht des dynamischen Versands darstellt, was einen leichten Leistungseinbruch bedeutet. Außerdem benötigt die Instanz zum Löschen von Typen zusätzlichen Speicher über der zugrunde liegenden Instanz. Aus diesen Gründen ist das Löschen von Typen keine kostenlose Abstraktion.
Natürlich gibt es einige Arbeiten zum Einrichten der Typlöschung, aber es kann sehr nützlich sein, wenn eine generische Protokollabstraktion erforderlich ist. Dieses Muster befindet sich in der schnellen Standardbibliothek mit Typen wie
AnySequence
. Weiterführende Literatur: http://robnapier.net/erasureBONUS:
Wenn Sie entscheiden, dass Sie
Printer
überall dieselbe Implementierung injizieren möchten , können Sie einen praktischen Initialisierer bereitstellen, fürAnyPrinter
den dieser Typ injiziert wird.Dies kann eine einfache und trockene Methode sein, um Abhängigkeitsinjektionen für Protokolle auszudrücken, die Sie in Ihrer App verwenden.
quelle
fatalError()
), die in anderen Tutorials zur Typlöschung beschrieben wird.Adressierung Ihres aktualisierten Anwendungsfalls:
(Übrigens
Printable
ist es bereits ein Standard-Swift-Protokoll, daher möchten Sie wahrscheinlich einen anderen Namen auswählen, um Verwirrung zu vermeiden.)Um bestimmte Einschränkungen für Protokollimplementierer durchzusetzen, können Sie die Typealien des Protokolls einschränken. So erstellen Sie Ihre Protokollsammlung, für die die Elemente druckbar sein müssen:
Wenn Sie nun eine Sammlung implementieren möchten, die nur druckbare Elemente enthalten kann:
Dies ist jedoch wahrscheinlich von geringem Nutzen, da Sie vorhandene Swift-Auflistungsstrukturen nicht so einschränken können, sondern nur solche, die Sie implementieren.
Stattdessen sollten Sie generische Funktionen erstellen, die ihre Eingabe auf Sammlungen beschränken, die druckbare Elemente enthalten.
quelle