Thuốc generic được thực hiện như thế nào?


16

Đây là câu hỏi từ quan điểm nội bộ trình biên dịch.

Tôi quan tâm đến thuốc generic, không phải mẫu (C ++), vì vậy tôi đã đánh dấu câu hỏi bằng C #. Không phải Java, vì AFAIK, thuốc generic trong cả hai ngôn ngữ đều khác nhau về cách triển khai.

Khi tôi nhìn vào các ngôn ngữ không có tính tổng quát, nó khá đơn giản, bạn có thể xác thực định nghĩa lớp, thêm nó vào hệ thống phân cấp và đó là nó.

Nhưng phải làm gì với lớp chung, và quan trọng hơn là làm thế nào để xử lý các tham chiếu đến nó? Làm thế nào để đảm bảo rằng các trường tĩnh là số ít trên mỗi lần xuất hiện (tức là mỗi lần các tham số chung được giải quyết).

Hãy nói rằng tôi thấy một cuộc gọi:

var x = new Foo<Bar>();

Tôi có thêm Foo_Barlớp mới vào hệ thống phân cấp không?


Cập nhật: Cho đến nay tôi chỉ tìm thấy 2 bài viết có liên quan, tuy nhiên thậm chí chúng không đi sâu vào chi tiết theo nghĩa "làm thế nào để tự làm":


Nâng cao bởi vì tôi nghĩ rằng một câu trả lời đầy đủ sẽ thú vị. Tôi có một số ý tưởng về cách nó hoạt động nhưng không đủ để trả lời chính xác. Tôi không nghĩ rằng thuốc generic trong C # biên dịch thành các lớp chuyên biệt cho từng loại chung. Chúng dường như được giải quyết trong thời gian chạy (có thể có một tốc độ đáng chú ý từ việc sử dụng thuốc generic). Có lẽ chúng ta có thể khiến Eric Lippert hòa nhập?
KChaloux

2
@KChaloux: Ở cấp độ MSIL, có một mô tả về cái chung. Khi JIT chạy, nó tạo mã máy riêng cho từng loại giá trị được sử dụng làm tham số chung và thêm một bộ mã máy bao gồm tất cả các loại tham chiếu. Bảo tồn mô tả chung trong MSIL thực sự tốt vì nó cho phép bạn tạo các phiên bản mới khi chạy.
Ben Voigt

@Ben Đó là lý do tại sao tôi không cố gắng trả lời câu hỏi: p
KChaloux

Tôi không chắc chắn nếu bạn vẫn còn xung quanh, nhưng những gì ngôn ngữ mà bạn biên soạn để . Điều đó sẽ có rất nhiều ảnh hưởng đến cách bạn thực hiện thuốc generic. Tôi có thể cung cấp thông tin về cách tôi thường tiếp cận nó ở mặt trước, nhưng mặt sau có thể thay đổi dữ dội.
Telastyn

@Telastyn, đối với những chủ đề đó chắc chắn tôi là :-) Tôi đang tìm kiếm thứ gì đó thực sự gần với C #, trong trường hợp của tôi, tôi đang biên dịch sang PHP (không đùa). Tôi sẽ biết ơn nếu bạn chia sẻ kiến ​​thức của bạn.
greenoldman

Câu trả lời:


4

Làm thế nào để đảm bảo rằng các trường tĩnh là số ít trên mỗi lần xuất hiện (tức là mỗi lần các tham số chung được giải quyết).

Mỗi khởi tạo chung có một bản sao riêng của Phương thức (được đặt tên khó hiểu), là nơi lưu trữ các trường tĩnh.

Hãy nói rằng tôi thấy một cuộc gọi:

var x = new Foo<Bar>();

Tôi có thêm Foo_Barlớp mới vào hệ thống phân cấp không?

Tôi không chắc sẽ hữu ích khi nghĩ về hệ thống phân cấp lớp như một số cấu trúc thực sự tồn tại trong thời gian chạy, nó có cấu trúc logic hơn.

Nhưng nếu bạn xem xét các Phương thức, mỗi phương thức có một con trỏ gián tiếp đến lớp cơ sở của nó, để tạo thành hệ thống phân cấp này, thì vâng, điều này sẽ thêm lớp mới vào hệ thống phân cấp.


Cảm ơn bạn, đó là mảnh thú vị. Vì vậy, các trường tĩnh được giải quyết tương tự như bảng ảo, phải không? Có một tài liệu tham khảo cho từ điển "toàn cầu" chứa các mục theo từng loại? Vì vậy, tôi có thể có 2 hội đồng không biết nhau bằng cách sử dụng Foo<string>và họ sẽ không tạo ra hai trường hợp từ trường tĩnh Foo.
greenoldman

1
@greenoldman Vâng, không giống với bảng ảo, hoàn toàn giống nhau. MethodTable giữ cả các trường tĩnh và tham chiếu đến các phương thức của loại, được sử dụng trong công văn ảo (đó là lý do tại sao nó được gọi là MethodTable). Và vâng, CLR phải có một số bảng mà nó có thể sử dụng để truy cập tất cả các Phương thức.
Svick

2

Tôi thấy hai câu hỏi cụ thể thực tế trong đó. Có lẽ bạn muốn hỏi thêm các câu hỏi liên quan (như câu hỏi riêng biệt có liên kết quay lại câu hỏi này) để có được sự hiểu biết đầy đủ.

Làm thế nào là các trường tĩnh được đưa ra các trường hợp riêng biệt cho mỗi trường hợp chung?

Chà, đối với các thành viên tĩnh không liên quan đến các tham số loại chung, điều này khá dễ dàng (sử dụng một từ điển được ánh xạ từ các tham số chung đến giá trị).

Các thành viên (tĩnh hoặc không) có liên quan đến các tham số loại có thể được xử lý thông qua việc xóa loại. Chỉ cần sử dụng bất cứ điều gì ràng buộc mạnh nhất là (thường System.Object). Vì thông tin loại bị xóa sau khi kiểm tra loại trình biên dịch, điều đó có nghĩa là kiểm tra loại thời gian chạy sẽ không cần thiết (mặc dù các giao diện vẫn có thể tồn tại trong thời gian chạy).

Có phải mỗi trường hợp chung xuất hiện riêng trong hệ thống phân cấp loại?

Không phải trong .NET generic. Quyết định đã được đưa ra để loại trừ sự kế thừa từ các tham số loại, do đó, hóa ra tất cả các trường hợp của một cái chung chiếm cùng một vị trí trong hệ thống phân cấp loại.

Đây có lẽ là một quyết định tốt, bởi vì việc không tìm kiếm tên từ một lớp cơ sở sẽ vô cùng đáng ngạc nhiên.


Vấn đề của tôi là tôi không thể thoát khỏi suy nghĩ về mặt mẫu. Ví dụ - không giống như lớp chung mẫu được biên dịch đầy đủ. Điều này có nghĩa là trong hội đồng khác sử dụng lớp này điều gì xảy ra? Phương pháp đã được biên dịch được gọi với nội bộ đúc? Tôi nghi ngờ các tổng quát có thể dựa vào ràng buộc - thay vào đó là đối số, nếu không Foo<int>Foo<string>sẽ đánh cùng một dữ liệu với các Fooràng buộc w / o.
greenoldman

1
@greenoldman: Chúng ta có thể tránh các loại giá trị trong một phút không, vì chúng thực sự được xử lý đặc biệt? Nếu bạn có List<string>List<Form>, sau đó vì List<T>bên trong có một thành viên loại T[]và không có ràng buộc nào T, thì cái bạn thực sự sẽ nhận được là mã máy thao túng một object[]. Tuy nhiên, vì chỉ các Ttrường hợp được đưa vào mảng, mọi thứ xuất hiện có thể được trả về dưới dạng Tkhông có kiểm tra loại bổ sung. Mặt khác, nếu bạn có ControlCollection<T> where T : Control, thì mảng bên trong T[]sẽ trở thành Control[].
Ben Voigt

Tôi có hiểu chính xác không, rằng ràng buộc được lấy và sử dụng làm tên kiểu nội bộ, nhưng khi lớp thực sự được sử dụng thì việc truyền được sử dụng? OK, tôi hiểu mô hình đó, nhưng tôi có ấn tượng Java sử dụng nó, không phải C #.
greenoldman

3
@greenoldman: Java thực hiện việc xóa kiểu trong bước dịch mã nguồn>> mã byte. Điều này làm cho trình xác minh không thể xác minh mã chung. C # thực hiện nó trong bước mã byte-> mã máy.
Ben Voigt

@BenVoigt Một số thông tin được lưu lại trong Java về các loại chung, vì nếu không, bạn không thể biên dịch theo lớp sử dụng chung mà không có nguồn. Nó chỉ không được giữ trong chuỗi mã byte chính AIUI, mà là trong siêu dữ liệu lớp.
Donal Fellows

1

Nhưng phải làm gì với lớp chung, và quan trọng hơn là làm thế nào để xử lý các tham chiếu đến nó?

Cách chung trong phần đầu của trình biên dịch là có hai loại thể hiện, kiểu chung ( List<T>) và kiểu chung ràng buộc ( List<Foo>). Kiểu chung xác định chức năng nào tồn tại, trường nào và có tham chiếu loại chung ở mọi nơi Tđược sử dụng. Kiểu chung bị ràng buộc chứa tham chiếu đến kiểu chung và một tập hợp các đối số kiểu. Điều đó có đủ thông tin để bạn tạo ra một loại cụ thể, thay thế các tham chiếu loại chung bằng Foohoặc bất kỳ đối số loại nào. Kiểu phân biệt này rất quan trọng khi bạn thực hiện suy luận kiểu và cần suy luận List<T>so với List<Foo>.

Thay vì nghĩ về các khái quát như các mẫu (trực tiếp xây dựng các triển khai khác nhau), thay vào đó, có thể hữu ích khi nghĩ về chúng như các hàm tạo kiểu ngôn ngữ chức năng (trong đó các đối số chung giống như các đối số thành một hàm cung cấp cho bạn một loại).

Về phần cuối, tôi không thực sự biết. Tất cả công việc của tôi với thuốc generic đã nhắm mục tiêu CIL là phụ trợ, vì vậy tôi có thể biên dịch chúng thành thuốc generic được hỗ trợ ở đó.


Cảm ơn bạn rất nhiều (đáng tiếc tôi không thể chấp nhận nhiều câu trả lời). Thật tuyệt khi biết rằng tôi đã thực hiện khá chính xác bước đó - trong trường hợp của tôi List<T>giữ kiểu thực (định nghĩa của nó), trong khi List<Foo>(cảm ơn bạn vì phần thuật ngữ cũng như vậy) với cách tiếp cận của tôi giữ các tuyên bố về List<T>(tất nhiên bây giờ bị ràng buộc với Foothay vì T).
greenoldman
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.