简体   繁体   中英

Change type of generic method in generic class

Please consider the following classes:

// Models:
class A {}
class B: A { }

// Parsers:
class AbstractParser<T> {}
class ParserB<T: B>: AbstractParser<T> {}

// Services:
class AbstractService<T> {
    func parser() -> AbstractParser<T> {
        fatalError("This method must be overridden")
    }
}
class ServiceA<T: A>: AbstractService<T> {
}
class ServiceB<T: B>: ServiceA<T> {
    private let _parser = ParserB()

    override func parser() -> ParserB<B> {
        return _parser
    }
}

I'm getting an error Method doesn not override any method from it's superclasses at overriden parser function. I could easily fix this by changing

class ServiceB<T: B>: ServiceA<T>

to

class ServiceB<T: B>: ServiceA<B>

but this will break a solution from this question: A variable in generic class gets wrong type

Is there any workaround for this?

EDIT

Thanks, Kenneth Bruno, your approach works, but it again leads to another error with types.

I add class C :

class C {
    var item = B()
}

and a simple method to ServiceB :

    func doSomething() {
        var entities = [T]()
        let c = C()
        entities.append(c.item)
    }

This causes error: Cannot invoke 'append' method with an argument list of type '(B)' . It seems the compiler can't understand that B and T are the same thing?

Also please note that I can't define var entities = [B]() , as I need to pass this array to another function in AbstractService method.

Just as in your other question you need to use the generic type instead of a specific type, then the method signatures will match to override the function.

class ServiceB<T: B>: ServiceA<T> {
  private let _parser = ParserB<T>()

  override func parser() -> ParserB<T> {
    return _parser
  }
}

From the question edit:

This causes error: Cannot invoke 'append' method with an argument list of type '(B)' . It seems the compiler can't understand that B and T are the same thing?


Just to clarify things. In the edit code example <T: B> and B are not the same thing. B is a regular type, while <T: B> is a generic type, which may represent a B type or any of it's subtypes.

Merging the question code with the code proposed by @Kenneth results in the following, which leads to a type error

class C {
    var item = B()
}

class ServiceB<T: B>: ServiceA<T> {
    private let _parser = ParserB<T>()

    override func parser() -> ParserB<T> {
        return _parser
    }

    func doSomething() {
        var entities = [T]()
        let c = C()
        entities.append(c.item) // Error: Cannot invoke 'append' method with an argument list of type '(B)'
    }
}

Now let's say in the future we add a new type D , subtype of B and instantiate a ServiceB<D> . This would cause the function doSomething() to try to append an instance of B in an array of D which is illegal, that's why the compiler raises an error.

With the code proposed in the comments by @Kenneth, the entities array would be filled in the ServiceB<B> case, but would always be empty in the ServiceB<D> .

class D: B { }

class ServiceB<T: B>: ServiceA<T> {
    ...
    func doSomething() {
        var entities = [T]()
        let c = C()
        if let item = c.item as? T { entities.append(item) }
    }
}

let service = ServiceB<B>()
service.doSomething() // Creates an array of B and append a single B instance on it

let serviceD = ServiceB<D>()
serviceD.doSomething() // Creates an array of D, c.item of type B can't be cast to D, the array will be empty

While my answer doesn't really solves your problem, I think it should put you one step closer to a solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM