Disons que j'ai un protocole:
public protocol Printable {
typealias T
func Print(val:T)
}
Et voici la mise en œuvre
class Printer<T> : Printable {
func Print(val: T) {
println(val)
}
}
Je m'attendais à ce que je puisse utiliser une Printable
variable pour imprimer des valeurs comme celle-ci:
let p:Printable = Printer<Int>()
p.Print(67)
Le compilateur se plaint de cette erreur:
"Le protocole 'Imprimable' ne peut être utilisé que comme contrainte générique car il a des exigences de type Self ou associé"
Est-ce que je fais quelque chose de mal ? Aucun moyen de réparer cela ?
**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: exemple du monde réel de ce que je veux. Notez que cela ne compilera pas, mais présente ce que je veux réaliser.
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()
}
Réponses:
Comme Thomas le souligne, vous pouvez déclarer votre variable en ne donnant pas du tout un type (ou vous pouvez le donner explicitement comme type
Printer<Int>
. Mais voici une explication de la raison pour laquelle vous ne pouvez pas avoir de type dePrintable
protocole.Vous ne pouvez pas traiter les protocoles avec des types associés comme des protocoles réguliers et les déclarer comme des types de variables autonomes. Pour réfléchir à pourquoi, considérez ce scénario. Supposons que vous ayez déclaré un protocole pour stocker un type arbitraire, puis le récupérer:
// a general protocol that allows for storing and retrieving // a specific type (as defined by a Stored typealias protocol StoringType { typealias Stored init(_ value: Stored) func getStored() -> Stored } // An implementation that stores Ints struct IntStorer: StoringType { typealias Stored = Int private let _stored: Int init(_ value: Int) { _stored = value } func getStored() -> Int { return _stored } } // An implementation that stores Strings struct StringStorer: StoringType { typealias Stored = String private let _stored: String init(_ value: String) { _stored = value } func getStored() -> String { return _stored } } let intStorer = IntStorer(5) intStorer.getStored() // returns 5 let stringStorer = StringStorer("five") stringStorer.getStored() // returns "five"
OK, jusqu'ici tout va bien.
Maintenant, la principale raison pour laquelle vous auriez un type de variable soit un protocole qu'un type implémente, plutôt que le type réel, est que vous pouvez assigner différents types d'objets qui sont tous conformes à ce protocole à la même variable, et devenir polymorphe comportement à l'exécution en fonction de ce qu'est réellement l'objet.
Mais vous ne pouvez pas faire cela si le protocole a un type associé. Comment le code suivant fonctionnerait-il en pratique?
// as you've seen this won't compile because // StoringType has an associated type. // randomly assign either a string or int storer to someStorer: var someStorer: StoringType = arc4random()%2 == 0 ? intStorer : stringStorer let x = someStorer.getStored()
Dans le code ci-dessus, quel serait le type de
x
? UnInt
? Ou unString
? Dans Swift, tous les types doivent être corrigés au moment de la compilation. Une fonction ne peut pas passer dynamiquement du retour d'un type à un autre en fonction de facteurs déterminés lors de l'exécution.Au lieu de cela, vous ne pouvez utiliser
StoredType
que comme contrainte générique. Supposons que vous vouliez imprimer n'importe quel type de type stocké. Vous pouvez écrire une fonction comme celle-ci:func printStoredValue<S: StoringType>(storer: S) { let x = storer.getStored() println(x) } printStoredValue(intStorer) printStoredValue(stringStorer)
C'est OK, car au moment de la compilation, c'est comme si le compilateur écrivait deux versions de
printStoredValue
: une pourInt
s et une pourString
s. Dans ces deux versions,x
est connu pour être d'un type spécifique.la source
p
variable, puis passiez des types non valides dansprint
? Exception d'exécution?var someStorer: StoringType<Int>
ouvar someStorer: StoringType<String>
et résoudre le problème que vous décrivez.Il y a une autre solution qui n'a pas été mentionnée sur cette question, qui utilise une technique appelée effacement de type . Pour obtenir une interface abstraite pour un protocole générique, créez une classe ou une structure qui encapsule un objet ou une structure conforme au protocole. La classe wrapper, généralement nommée «Any {protocol name}», se conforme elle-même au protocole et implémente ses fonctions en transférant tous les appels à l'objet interne. Essayez l'exemple ci-dessous dans une aire de jeux:
import Foundation public protocol Printer { typealias T func print(val:T) } struct AnyPrinter<U>: Printer { typealias T = U private let _print: U -> () init<Base: Printer where Base.T == U>(base : Base) { _print = base.print } func print(val: T) { _print(val) } } struct NSLogger<U>: Printer { typealias T = U func print(val: T) { NSLog("\(val)") } } let nsLogger = NSLogger<Int>() let printer = AnyPrinter(base: nsLogger) printer.print(5) // prints 5
Le type de
printer
est connuAnyPrinter<Int>
et peut être utilisé pour résumer toute implémentation possible du protocole d'imprimante. Bien qu'AnyPrinter ne soit pas techniquement abstrait, son implémentation n'est qu'une chute vers un type d'implémentation réel et peut être utilisée pour découpler les types d'implémentation des types qui les utilisent.Une chose à noter est qu'il
AnyPrinter
n'est pas nécessaire de conserver explicitement l'instance de base. En fait, nous ne pouvons pas car nous ne pouvons pas déclarerAnyPrinter
avoir unePrinter<T>
propriété. Au lieu de cela, nous obtenons un pointeur de fonction_print
vers laprint
fonction de base . L'appelbase.print
sans l'invoquer renvoie une fonction où la base est curry comme variable auto, et est donc conservée pour les futures invocations.Une autre chose à garder à l'esprit est que cette solution est essentiellement une autre couche de répartition dynamique, ce qui signifie un léger impact sur les performances. En outre, l'instance d'effacement de type nécessite une mémoire supplémentaire en plus de l'instance sous-jacente. Pour ces raisons, l'effacement de caractères n'est pas une abstraction gratuite.
De toute évidence, il y a du travail pour configurer l'effacement de type, mais cela peut être très utile si une abstraction de protocole générique est nécessaire. Ce modèle se trouve dans la bibliothèque standard swift avec des types comme
AnySequence
. Lectures complémentaires: http://robnapier.net/erasurePRIME:
Si vous décidez que vous souhaitez injecter la même implémentation de
Printer
partout, vous pouvez fournir un initialiseur pratique pourAnyPrinter
lequel injecte ce type.extension AnyPrinter { convenience init() { let nsLogger = NSLogger<T>() self.init(base: nsLogger) } } let printer = AnyPrinter<Int>() printer.print(10) //prints 10 with NSLog
Cela peut être un moyen simple et SEC d'exprimer des injections de dépendances pour les protocoles que vous utilisez dans votre application.
la source
fatalError()
) qui est décrite dans d'autres tutoriels d'effacement de type.Répondre à votre cas d'utilisation mis à jour:
(btw
Printable
est déjà un protocole Swift standard, vous voudrez probablement choisir un nom différent pour éviter toute confusion)Pour appliquer des restrictions spécifiques aux implémenteurs de protocole, vous pouvez contraindre les typealias du protocole. Donc, pour créer votre collection de protocoles qui nécessite que les éléments soient imprimables:
// because of how how collections are structured in the Swift std lib, // you’d first need to create a PrintableGeneratorType, which would be // a constrained version of GeneratorType protocol PrintableGeneratorType: GeneratorType { // require elements to be printable: typealias Element: Printable } // then have the collection require a printable generator protocol PrintableCollectionType: CollectionType { typealias Generator: PrintableGenerator }
Maintenant, si vous vouliez implémenter une collection qui ne pouvait contenir que des éléments imprimables:
struct MyPrintableCollection<T: Printable>: PrintableCollectionType { typealias Generator = IndexingGenerator<T> // etc... }
Cependant, cela n'a probablement que peu d'utilité réelle, car vous ne pouvez pas contraindre les structures de collection Swift existantes comme celle-ci, uniquement celles que vous implémentez.
Au lieu de cela, vous devez créer des fonctions génériques qui contraignent leur entrée à des collections contenant des éléments imprimables.
func printCollection <C: CollectionType where C.Generator.Element: Printable> (source: C) { for x in source { x.print() } }
la source