2015-01-13 16 views
27

Tôi đang cố gắng thực hiện cấu trúc sau bằng cách sử dụng Generics. Bắt một lỗi trình biên dịch lạ, không thể hiểu tại sao.Lỗi generus lạ

class Translator<T:Hashable> {...} 

class FooTranslator<String>:Translator<String> {...} 

Ý tưởng là Translator sử dụng T làm loại khóa trong từ điển. Điều này có thể là ví dụ một chuỗi hoặc một enum. Phân lớp cung cấp từ điển cụ thể.

Nhưng nó không thành công vì điều này: "Loại 'String' không phù hợp với giao thức 'Hashable"

Nhưng Chuỗi phù hợp với Hashable! Tôi có hạt không? Nó cũng không hoạt động với Int, nó cũng tương thích với Hashable. Nó cũng không hoạt động nếu tôi thay thế Hashable bằng Equatable, cũng nên được thực hiện bởi cả hai.

Nếu tôi loại bỏ các loại hạn chế, chỉ để thử nghiệm (nơi tôi cũng phải vô hiệu hóa các từ điển, như tôi không thể sử dụng bất cứ điều gì không hashable như chìa khóa có) - nó biên dịch

class Translator<T> {...} 

class FooTranslator<String>:Translator<String> {...} 

Tôi gì làm sai?

Trả lời

17

Tôi không phải là nhà phát triển Swift, nhưng đã gặp sự cố tương tự trong Java, tôi nghi ngờ rằng tại thời điểm bạn tuyên bố tham số kiểu được gọi là String vì bạn đang khai báo class FooTranslator<String> - do đó đối số loại trong Translator<String> chỉ là tham số kiểu, không có ràng buộc. Bạn không muốn một tham số kiểu nào cả, tôi nghi ngờ (ví dụ: bạn không muốn bạn FooTranslator là một lớp chung của chính nó.)

Như đã nêu trong các ý kiến, trong Swift subclasses of a generic class also have to be generic. Bạn có thể có thể khai báo một tham số kiểu ném đi, như thế này:

class FooTranslator<T>:Translator<String> 

mà vẫn tránh khai báo một tham số kiểu mới gọi là String, đó là những gì đã gây ra vấn đề. Điều đó có nghĩa là bạn đang giới thiệu một thông số loại mới khi bạn không muốn bất kỳ thông số loại nào, nhưng nó có thể tốt hơn không ...

Đây là tất cả trên giả định rằng bạn thực sự cần một phân lớp, ví dụ: để thêm hoặc ghi đè thành viên. Mặt khác, nếu bạn chỉ muốn một loại đó là chính xác giống như Translator<String>, bạn nên sử dụng một loại bí danh thay vì:

typealias FooTranslator = Translator<String> 

Hoặc thậm chí kết hợp hai trong một cách khủng khiếp, nếu bạn thực sự muốn một lớp con nhưng không muốn phải đề cập đến nó trong một cách chung chung:

class GenericFooTranslator<T>:Translator<String> 
typealias FooTranslator = GenericFooTranslator<Int> 

(Lưu ý rằng Int ở đây là cố tình không String, để chứng minh rằng các T trong Translator là không giống như các T trong FooTranslator.)

+0

Không phải vậy. Có vấn đề/giới hạn Swift liên quan đến vấn đề này, xem http://stackoverflow.com/questions/24138359/limitation-with-classes-derived-from-generic-classes-in-swift – Ixx

+0

@lxx: Ick. Tôi nghi ngờ đó vẫn là nguyên nhân *, nhưng bạn muốn có một giải pháp khác. Tôi đã chỉnh sửa câu trả lời của mình bằng một giải pháp khả thi để thử. –

+0

Vâng, vâng, giải pháp với thông số loại bỏ đi hoạt động nhưng tôi đã không thử nó bởi vì nó xấu xí. Ở phía bên kia, tôi đã thử lại giải pháp typealias và nó hoạt động ngay bây giờ. Có một vấn đề bởi vì (câu trả lời được chọn) nó sử dụng Int làm trình giữ chỗ, đó là lý do tại sao tôi đã thử sử dụng String, tham số và nó đã gây ra lỗi. Tôi đã thay thế bằng T và tuyên bố typealias dưới lớp, không phải nó biên dịch. Nó vẫn là một cách giải quyết mặc dù. Cảm ơn anyways đã chỉ cho tôi trở lại giải pháp chính xác :) – Ixx

96

Đối với người mới bắt đầu, chúng ta hãy giải thích các lỗi:

Given:

class Foo<T:Hashable> { } 

class SubFoo<String> : Foo<String> { } 

Phần khó hiểu ở đây là chúng tôi mong đợi "String" có nghĩa là cấu trúc định nghĩa Swift nắm giữ một bộ sưu tập của các nhân vật. Nhưng không phải vậy.

Ở đây, "Chuỗi" là tên của loại chung mà chúng tôi đã cung cấp cho lớp con mới, SubFoo. Điều này trở nên rất rõ ràng nếu chúng tôi thực hiện một số thay đổi:

class SubFoo<String> : Foo<T> { } 

Đường này tạo lỗi cho T khi sử dụng loại không khai báo.

Sau đó, nếu chúng ta thay đổi dòng này:

class SubFoo<T> : Foo<T> { } 

Chúng tôi đang trở lại với lỗi tương tự bạn ban đầu đã có, 'T' không phù hợp với 'Hashable'. Điều này hiển nhiên ở đây, vì T không gây nhầm lẫn tên của một kiểu Swift hiện có xảy ra để phù hợp với 'Hashable'. Rõ ràng 'T' là một chung chung.

Khi chúng tôi viết 'Chuỗi', nó cũng chỉ là tên trình giữ chỗ cho loại chung, và không thực sự là loại String tồn tại trong Swift.


Nếu chúng ta muốn có một tên khác nhau cho một loại hình cụ thể của một lớp chung, cách tiếp cận thích hợp là gần như chắc chắn một typealias:

class Foo<T:Hashable> { 

} 

typealias StringFoo = Foo<String> 

này là hoàn toàn hợp lệ Swift, và nó biên dịch tốt.


Nếu thay vào đó những gì chúng tôi muốn thực sự là phân lớp và thêm phương thức hoặc thuộc tính vào lớp chung, thì chúng tôi cần cụ thể hơn cho những gì chúng tôi cần.

Trở lại với vấn đề ban đầu, chúng ta hãy đầu tiên thoát khỏi các lỗi:

class Foo<T: Hashable> 

class SubFoo<T: Hashable> : Foo<T> { } 

này là hoàn toàn hợp lệ Swift. Nhưng nó có thể không đặc biệt hữu ích cho những gì chúng tôi đang làm.

Lý do duy nhất chúng ta không thể làm như sau:

class SubFoo<T: String> : Foo<T> { } 

chỉ đơn giản là vì String không phải là một lớp Swift - đó là một cấu trúc. Và điều này không được phép cho bất kỳ cấu trúc nào.


Nếu chúng ta viết một giao thức mới được thừa kế từ Hashable, chúng tôi có thể sử dụng:

protocol MyProtocol : Hashable { } 

class Foo<T: Hashable> { } 
class SubFoo<T: MyProtocol> : Foo<T> { } 

này là hoàn toàn hợp lệ.

Ngoài ra, lưu ý rằng chúng tôi không thực sự phải kế thừa từ Hashable:

protocol MyProtocol { } 
class Foo<T: Hashable> { } 
class SubFoo<T: Hashable, MyProtocol> { } 

Đây cũng là hoàn toàn hợp lệ.

Tuy nhiên, lưu ý rằng, vì lý do gì đó, Swift sẽ không cho phép bạn sử dụng lớp học ở đây. Ví dụ:

class MyClass : Hashable { } 

class Foo<T: Hashable> { } 
class SubFoo<T: MyClass> : Foo<T> { } 

Swift bí ẩn phàn nàn rằng 'T' không phù hợp với 'Hashable' (ngay cả khi chúng ta thêm đoạn mã cần thiết để làm cho nó để


Cuối cùng, bên phải. phương pháp tiếp cận và cách tiếp cận phù hợp nhất với Swift sẽ là viết một giao thức mới kế thừa từ 'Hashable' và thêm bất kỳ chức năng nào vào nó mà bạn cần. Điều quan trọng là wha tever lớp con của chúng tôi có, nó có các phương pháp cần thiết và tài sản mà chúng tôi cần cho bất cứ điều gì chúng tôi đang làm.