Cả hai câu lệnh liên quan đến việc gọi hàm tạo mặc định là Dog
khi bạn tự đề cập đến; do đó, rõ ràng là trong cả hai trường hợp, cá thể Dog
được tạo. Điều này có nghĩa là cả hai câu lệnh đều kết thúc khởi tạo biến số với một ví dụ giống hệt nhau (đây là một phần của câu lệnh sau dấu bằng).
Tuy nhiên, các câu lệnh cũng có một phần khác: khai báo của biến số (đây là một phần của câu lệnh trước dấu bằng). Trong các ngôn ngữ kiểu tĩnh như C#, mỗi biến - tổng quát hơn, bất kỳ biểu hiện - có một loại tĩnh:
object a = new Dog(); // static type: object/runtime type: Dog
Dog b = new Dog(); // static type: Dog/runtime type: Dog
Trình biên dịch sẽ không cho phép bạn gán giá trị cho một biến mà nó không thể chứng minh là loại tĩnh của biến, ví dụ nó sẽ không cho phép
Cat c = new Dog(); // unless Dog derives from Cat, which we know isn't true
Vì tất cả reference types ngầm xuất phát từ System.Object
, gán một Dog
đến một biến kiểu tĩnh object
là OK. Bạn có thể nghĩ về "loại tĩnh" như đối tượng được "khai báo là". Bạn luôn có thể xác định loại tĩnh của một cái gì đó chỉ bằng cách đọc mã nguồn; đây là cách trình biên dịch thực hiện nó.
Sau đó, cũng có loại thời gian chạy của mỗi biến (biểu thức), mà tôi đã đề cập ở trên. Điều này là như nhau trong cả hai trường hợp, bởi vì sau tất cả trong cả hai trường hợp, chúng tôi đã tạo ra một Dog
. Bạn có thể nghĩ về "loại thời gian chạy" như đối tượng thực sự là. Loại thời gian chạy của một cái gì đó không thể được xác định chỉ bằng cách đọc nguồn; bạn chỉ xác định nó trong khi chương trình đang chạy, do đó tên. Trong C#, điều này được thực hiện bằng cách gọi GetType
.
Rõ ràng là loại thời gian chạy là thứ mà bạn không thể làm mà không có; mọi thứ đều phải "trở thành" gì đó. Nhưng tại sao lại bận tâm với việc phát minh ra khái niệm về kiểu tĩnh?
Bạn có thể nghĩ về static types làm hợp đồng giữa bạn (người lập trình) và trình biên dịch. Bằng cách khai báo kiểu tĩnh là b
là Dog
, bạn nói với trình biên dịch rằng bạn không có ý định sử dụng biến đó để lưu trữ bất kỳ thứ gì khác ngoài một số Dog
. Trình biên dịch, ngược lại, hứa hẹn sẽ không cho phép bạn vi phạm mục đích đã nêu của bạn và tạo ra một lỗi nếu bạn cố gắng làm điều đó. Việc này cũng ngăn bạn sử dụng d
theo bất kỳ cách nào không phải mỗi loại Dog
sẽ hỗ trợ.
xem xét:
class Dog {
public void Woof();
}
Dog d = new Dog();
d.Woof(); // OK
object o = new Dog();
o.Woof(); // COMPILER ERROR
Dòng cuối cùng gây ra một lỗi biên dịch vì vi phạm hợp đồng gõ tĩnh: bạn nói với trình biên dịch rằng o
có thể bất cứ điều gì phát sinh từ System.Object
, nhưng không phải tất cả trong những điều bắt nguồn từ đó có phương thức Woof
. Vì vậy, trình biên dịch đang cố gắng bảo vệ bạn bằng cách nói "Bạn đang làm gì ở đó? Tôi không thể chứng minh rằng bất cứ điều gì trong o
có thể lừa đảo! Nếu đó là một Cat
thì sao?".
Ghi chú:
¹ này không có nghĩa rằng tất cả các đối tượng một cách kỳ diệu biết điều đó "là" những gì trong tất cả các ngôn ngữ. Trong một số trường hợp (ví dụ:trong C++) thông tin này có thể được sử dụng khi tạo một đối tượng, nhưng sau đó "bị lãng quên" để cho phép trình biên dịch tự do hơn để tối ưu hóa mã. Nếu điều này xảy ra, đối tượng vẫn là là điều gì đó, nhưng bạn không thể poke nó và hỏi nó "bạn là gì?".
² Thực ra, trong ví dụ tầm thường này, nó có thể chứng minh điều đó. Nhưng nó sẽ không chọn sử dụng kiến thức này bởi vì tôn trọng hợp đồng của loại tĩnh là toàn bộ vấn đề.
Vẻ đẹp của điều này tôi thấy là đôi khi, một phương pháp có thể cần phải làm việc với nhiều hơn một 'Dog'. Với câu lệnh 'Object a = new Dog()', bạn có thể sử dụng nó trong một phương thức. Tương tự như vậy, bạn có thể làm 'Object b = new Cat()', và bạn có thể sử dụng nó theo cùng một cách. –
@Ken Tại sao không sử dụng giao diện? Giống như HousePet a = new Dog() ;. Đặt các phương thức chia sẻ trong giao diện HousePet hoặc giao diện Động vật. – Andy
Đây là một bản sao ... – slugster