Genel bir işlevi açıkça özelleştiremezsiniz


92

Aşağıdaki kodla ilgili sorun yaşıyorum:

func generic1<T>(name : String){
}

func generic2<T>(name : String){
     generic1<T>(name)
}

derleyici hatası için genel1 (ad) sonucu "Genel bir işlev açıkça özelleştirilemez"

Bu hatayı önlemenin bir yolu var mı? Generic1 işlevinin imzasını değiştiremiyorum, bu nedenle (String) -> Void olmalı


2
Bağlam için çıkarsama yapılamadığında burada jenerik türü kullanmanın anlamı nedir? Genel tür yalnızca dahili olarak kullanılıyorsa, türü işlevin gövdesi içinde belirtmelisiniz.
Kirsteins

Yer tutucu türü hiç Tkullanılıyor generic1()mu? Derleyicinin türü çıkarabilmesi için bu işlevi nasıl çağırırsınız ?
Martin R

3
Genetik1 <blaClass> ("SomeString")
Greyisf

2
Jenerikler yalnızca derleyicinin bağlamı çıkarabildiği durumlar için değildir. Fonksiyonun açık bir şekilde özelleştirilmesi, kodun diğer kısımlarının da çıkarılmasına izin verir. örn: func foo<T>() -> T { ... }bu ttür bir nesne Tve dönüş ile bir şeyler yapar t. Açıkça uzmanlaşma T, t1ihlal edilmesine izin verir var t1 = foo<T>(). Keşke işlevleri bu şekilde çağırmanın bir yolu olsaydı. C # buna izin verir
nacho4d

1
Ben de bununla karşılaştım. Türü bir parametre olarak geçirmeye zorlanırsanız, genel bir işlev oluşturmak sıfır mantıklıdır. Bu bir hata olmalı, değil mi ?!
Nick

Yanıtlar:


161

Ayrıca bu sorunu yaşadım ve durumum için bir geçici çözüm buldum.

Bu yazıda yazarın aynı sorunu var

https://www.iphonelife.com/blog/31369/swift-programming-101-generics-practical-guide

Öyleyse sorun şu ki, derleyicinin bir şekilde T tipini çıkarması gerekiyor. Ancak genel <tür> (parametreler ...) kullanımına izin verilmez.

Normalde, derleyici parametre türlerini tarayarak T tipini arayabilir çünkü çoğu durumda T burada kullanılır.

Benim durumumda biraz farklıydı, çünkü fonksiyonumun dönüş tipi T idi. Sizin durumunuzda, T'yi fonksiyonunuzda hiç kullanmamışsınız gibi görünüyor. Sanırım örnek kodu basitleştirdiniz.

Bu yüzden aşağıdaki işleve sahibim

func getProperty<T>( propertyID : String ) -> T

Ve örneğin olması durumunda

getProperty<Int>("countProperty")

derleyici bana şu hatayı veriyor:

Genel bir işlevi açıkça özelleştiremezsiniz

Bu nedenle, derleyiciye T'nin türünü çıkarması için başka bir bilgi kaynağı vermek için, dönüş değerinin kaydedildiği değişkenin türünü açıkça belirtmeniz gerekir.

var value : Int = getProperty("countProperty")

Bu şekilde derleyici T'nin bir tamsayı olması gerektiğini bilir.

Dolayısıyla genel olarak bunun basitçe, genel bir fonksiyon belirtirseniz, parametre türlerinizde veya dönüş türü olarak en azından T kullanmanız gerektiği anlamına geldiğini düşünüyorum.


2
Bana çok zaman kazandırdı. Teşekkür ederim.
chrislarson

4
Dönüş değeri yoksa bunu yapmanın bir yolu var mı? yani,func updateProperty<T>( propertyID : String )
Kyle Bashour

1
Bunu neden yapmak isteyeceğini anlamıyorum? Hiçbir şey döndürmediğiniz için, işlevinizi genel olarak bildirmenin bir faydası yoktur.
ThottChief

@ThottChief'in pek çok faydası var, örneğin anahtar yolları kullanıyor / oluşturuyorsanız veya tür adını bir dizge olarak
alıyorsanız

Harika cevap, teşekkürler. Bunun işe yaramasını dilerdim, let value = foo() as? Typeböylece bir ifveya guardsonuç isteğe bağlı olarak kullanılabilir, ama değil ...
agirault

54

Swift 5

Genel olarak genel işlevleri tanımlamanın birçok yolu vardır. Ancak , bir veya bir olarak Tkullanılması gereken koşula dayalıdırlar .parameterreturn type

extension UIViewController {
    class func doSomething<T: UIView>() -> T {
        return T()
    }

    class func doSomethingElse<T: UIView>(value: T) {
        // Note: value is a instance of T
    }

    class func doLastThing<T: UIView>(value: T.Type) {
        // Note: value is a MetaType of T
    }
}

Bundan sonra, aradığımızda sağlamalıyız T.

let result = UIViewController.doSomething() as UIImageView // Define `T` by casting, as UIImageView
let result: UILabel = UIViewController.doSomething() // Define `T` with property type, as UILabel
UIViewController.doSomethingElse(value: UIButton()) // Define `T` with parameter type, as UIButton
UIViewController.doLastThing(value: UITextView.self) // Define `T` with parameter type, as UITextView

Referans:

  1. http://austinzheng.com/2015/01/02/swift-generics-pt-1/
  2. https://dispatchswift.com/type-constraints-for-generics-in-swift-d6bf2f0dbbb2

Genel soruna harika bir cevap ... çünkü onu düzeltmenin pek çok yolu var. Ayrıca self.result = UIViewController.doSomething()mülkü beyan ettiğinizde yazdığınız sürece kendi başına da işler fark ettim .
teradyl

bir çok şeyi açıklayan harika bir cevap.
Okhan Okbay

Java doLastThing<UITextView>()Swift oluyor doLastThing(UITextView.self), ki bu ... en azından en kötüsü değil. Karmaşık sonuçları açıkça yazmaktan daha iyidir. Çözüm için teşekkürler.
Erhannis

18

Çözüm, sınıf türünü parametre olarak almaktır (Java'daki gibi)

Derleyicinin hangi türle uğraştığını bilmesini sağlamak için sınıfı bağımsız değişken olarak iletmek için

extension UIViewController {
    func navigate<ControllerType: UIViewController>(_ dump: ControllerType.Type, id: String, before: ((ControllerType) -> Void)?){
        let controller = self.storyboard?.instantiateViewController(withIdentifier: id) as! ControllerType
        before?(controller)
        self.navigationController?.pushViewController(controller, animated: true)
    }
}

Şu numarayı arayın:

self.navigate(UserDetailsViewController.self, id: "UserDetailsViewController", before: {
        controller in
        controller.user = self.notification.sender
    })

1
Bu harika ve kabul edilen cevaptan çok daha iyi. Lütfen tarihi bölümü kaldırmak için düzenlemeyi düşünün veya en azından çözümün altına koyun. Teşekkürler!
Dan Rosenstark

1
@DanRosenstark Geri bildirim için teşekkürler :)
Orkhan Alikhanov

Bu işe yarasa da bunun en iyi uygulama olduğunu düşünmüyorum. Bunun nedeni, artık işlev tanımının, oyuncu kadrosunda bir sorun varsa ne yapılacağına dair kararlar almasıdır. Yani, burada sadece döküm başarısız olursa çarparsınız. Müşteriye göre değişiklik gösterebileceğinden, müşterinin ne yapacağına karar vermesine izin vermek daha iyidir. Bu nedenle, bu cevabı bu nedenle aşağı oylayacağım.
smileBot

@smileBot Örneğin kötü mü yoksa yaklaşım mı?
Orkhan Alikhanov

Yaklaşım. Pratik olduğunda uzmanlaşmayı ertelemenin genel bir programlama kuralı olduğunu düşünüyorum. Bu sorumluluğu anlama fikrinin bir parçasıdır. Jenerikte uzmanlaşmak fonksiyonun sorumluluğu değildir. Bu, arayanın sorumluluğudur, çünkü işlev tüm arayanların neye ihtiyaç duyacağını bilemez. İşlev bu role girerse, bu işlevin kazançsız kullanımını sınırlar. Bunu birçok kodlama problemine genelleştirebilirsiniz. Bu tek konsept bana çok yardımcı oldu.
smileBot

4

Statik türlere (parametre olarak Dize) sahip olduğunuz için burada bir jeneriğe ihtiyacınız yoktur, ancak genel bir işlevin başka birini çağırmasını istiyorsanız, aşağıdakileri yapabilirsiniz.

Genel yöntemleri kullanma

func fetchObjectOrCreate<T: NSManagedObject>(type: T.Type) -> T {
    if let existing = fetchExisting(type) {
       return existing
    }
    else {
        return createNew(type)
    }
}

func fetchExisting<T: NSManagedObject>(type: T.Type) -> T {
    let entityName = NSStringFromClass(type)
     // Run query for entiry
} 

func createNew<T: NSManagedObject>(type: T.Type) -> T {
     let entityName = NSStringFromClass(type)
     // create entity with name
} 

Genel bir sınıf kullanma (Genel, örnek başına yalnızca 1 tür için tanımlanabildiğinden daha az esnek)

class Foo<T> {

   func doStuff(text: String) -> T {
      return doOtherStuff(text)
   }

   func doOtherStuff(text: String) -> T {

   }  

}

let foo = Foo<Int>()
foo.doStuff("text")

3

Genel işlevi belirttiğinizde, aşağıdaki gibi T türünün bazı parametrelerini belirtmeniz gerektiğini düşünüyorum:

func generic1<T>(parameter: T) {
    println("OK")
}

func generic2<T>(parameter: T) {
    generic1(parameter)
}

ve handle () yöntemini çağırmak istiyorsanız, bunu protokol yazarak ve T için tür kısıtlaması belirterek yapabilirsiniz:

protocol Example {
    func handle() -> String
}

extension String: Example {
    func handle() -> String {
        return "OK"
    }
}

func generic1<T: Example>(parameter: T) {
    println(parameter.handle())
}

func generic2<T: Example>(parameter: T) {
    generic1(parameter)
}

bu nedenle bu genel işlevi String ile çağırabilirsiniz:

generic2("Some")

ve derlenecek


1

Şimdiye kadar, kişisel en iyi uygulamam @ orkhan-alikhanov'un cevabını kullandı. Bugün, SwiftUI bakarak ve nasıl ne zaman .modifier()ve ViewModifieruygulanan, başka bir yol buldum (ya da daha fazla bir geçici çözüm nedir?)

Basitçe ikinci işlevi bir struct.

Misal:

Bu size "Genel bir işlevi açıkça özelleştiremezsiniz" verirse

func generic2<T>(name: String){
     generic1<T>(name)
}

Bu yardımcı olabilir. Bildirimini generic1a struct:

struct Generic1Struct<T> {
    func generic1(name: String) {## do, whatever it needs with T ##}
}

ve şununla çağırın:

func generic2<T>(name : String){
     Generic1Struct<T>().generic1(name: name)
}

Uyarılar:

  • Bu hata mesajı oluştuğunda, olası herhangi bir durumda yardımcı olup olmadığını bilmiyorum. Bu ortaya çıktığında birçok kez takılıp kaldığımı biliyorum. Hata mesajı geldiğinde bu çözümün bugün yardımcı olduğunu biliyorum.
  • Swift'in Jenerikleri işleme şekli benim için hala kafa karıştırıcı.
  • Bu örnek ve ile ilgili geçici çözüm structiyi bir örnektir. Buradaki geçici çözüm biraz daha fazla bilgi içermez - ancak derleyiciyi geçer. Aynı bilgi, ancak farklı sonuçlar? O zaman bir sorun var. Bir derleyici hatası ise düzeltilebilir.

0

Genel sınıf işlevimle benzer bir sorun yaşadım class func retrieveByKey<T: GrandLite>(key: String) -> T?.

let a = retrieveByKey<Categories>(key: "abc")Kategorilerin GrandLite'ın bir alt sınıfı olduğu yerde onu arayamadım.

let a = Categories.retrieveByKey(key:"abc")Kategoriler değil, GrandLite döndürdü. Soysal işlevler, onları çağıran sınıfa dayalı olarak tür çıkarmazlar.

class func retrieveByKey<T: GrandLite>(aType: T, key: String>) -> T?Denediğimde let a = Categories.retrieveByKey(aType: Categories, key: "abc")bana bir hata verdi, Kategoriler, GrandLite'ın bir alt sınıfı olmasına rağmen, Categories.Type'ı GrandLite'a dönüştüremedi. ANCAK...

class func retrieveByKey<T: GrandLite>(aType: [T], key: String) -> T? let a = Categories.retrieveByKey(aType: [Categories](), key: "abc")Görünüşe göre bir alt sınıfın açık bir atamasını denediysem işe yaradı , ancak başka bir genel tür (dizi) kullanan örtük bir atama Swift 3'te çalışıyor.


1
Hatayı düzeltmek için kendini koymak yerine aTypeörneğini sağlamalısınız . Ör: . Diğer bir çözüm ise tanımlama . Ardından yöntemi şu şekilde çağırın:TCategorieslet a = Categories.retrieveByKey(aType: Categories(), key: "abc")aType: T.Typelet a = Categories.retrieveByKey(aType: Categories.self, key: "abc")
nahung89
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.