Ngay cả khi bạn có thể thấy chúng tương đương bằng cách nào đó thì chúng hoàn toàn khác nhau về mục đích. Đầu tiên chúng ta hãy thử định nghĩa diễn viên là gì:
Truyền là hành động thay đổi một thực thể của một kiểu dữ liệu này thành một thực thể khác.
Nó hơi chung chung và bằng cách nào đó nó tương đương với một chuyển đổi bởi vì một phép ép kiểu thường có cùng một cú pháp của một chuyển đổi, vì vậy câu hỏi nên là khi nào một phép ép kiểu (ẩn hoặc rõ ràng) được cho phép bởi ngôn ngữ và khi nào bạn phải sử dụng ( hơn) chuyển đổi rõ ràng?
Đầu tiên hãy để tôi vẽ một đường đơn giản giữa chúng. Về mặt hình thức (ngay cả khi tương đương với cú pháp ngôn ngữ) một phép ép kiểu sẽ thay đổi kiểu trong khi một chuyển đổi sẽ / có thể thay đổi giá trị (cuối cùng cùng với kiểu). Ngoài ra, một phép truyền có thể hoàn nguyên trong khi một chuyển đổi có thể không.
Chủ đề này khá rộng lớn vì vậy chúng ta hãy cố gắng thu hẹp nó một chút bằng cách loại trừ các toán tử diễn viên tùy chỉnh khỏi trò chơi.
Phôi ngầm
Trong C #, một phép ép kiểu ngầm khi bạn sẽ không mất bất kỳ thông tin nào (xin lưu ý rằng việc kiểm tra này được thực hiện với các loại chứ không phải với các giá trị thực của chúng ).
Các loại nguyên thủy
Ví dụ:
int tinyInteger = 10;
long bigInteger = tinyInteger;
float tinyReal = 10.0f;
double bigReal = tinyReal;
Những loại này là ẩn vì trong quá trình chuyển đổi, bạn sẽ không mất bất kỳ thông tin nào (bạn chỉ làm cho loại rộng hơn). Ngược lại, không được phép truyền ngầm định bởi vì, bất kể giá trị thực của chúng là bao nhiêu (vì chúng chỉ có thể được kiểm tra tại thời điểm chạy), trong quá trình chuyển đổi, bạn có thể mất một số thông tin. Ví dụ: mã này sẽ không biên dịch vì a double
có thể chứa (và thực tế là có) một giá trị không thể biểu diễn bằng float
:
double bigReal = Double.MaxValue;
float tinyReal = bigReal;
Các đối tượng
Trong trường hợp một đối tượng (một con trỏ tới) thì việc ép kiểu luôn luôn ẩn khi trình biên dịch có thể chắc chắn rằng kiểu nguồn là một lớp dẫn xuất (hoặc nó triển khai) kiểu của lớp đích, ví dụ:
string text = "123";
IFormattable formattable = text;
NotSupportedException derivedException = new NotSupportedException();
Exception baseException = derivedException;
Trong trường hợp này, trình biên dịch biết rằng string
thực thi IFormattable
và đó NotSupportedException
là (dẫn xuất từ) Exception
vì vậy ép kiểu là ngầm định. Không có thông tin nào bị mất bởi vì các đối tượng không thay đổi kiểu của chúng (điều này khác với kiểu struct
s và kiểu nguyên thủy vì với kiểu ép kiểu bạn tạo một đối tượng mới thuộc kiểu khác ), cái nhìn của bạn về chúng sẽ thay đổi gì .
Diễn viên rõ ràng
Việc ép kiểu là rõ ràng khi quá trình chuyển đổi không được trình biên dịch thực hiện ngầm và khi đó bạn phải sử dụng toán tử ép kiểu. Thông thường nó có nghĩa là:
- Bạn có thể mất thông tin hoặc dữ liệu vì vậy bạn phải biết về nó.
- Việc chuyển đổi có thể không thành công (vì bạn không thể chuyển đổi loại này sang loại khác) vì vậy, một lần nữa, bạn phải biết mình đang làm gì.
Các loại nguyên thủy
Một kiểu truyền rõ ràng là bắt buộc đối với các kiểu nguyên thủy khi trong quá trình chuyển đổi, bạn có thể mất một số dữ liệu, ví dụ:
double precise = Math.Cos(Math.PI * 1.23456) / Math.Sin(1.23456);
float coarse = (float)precise;
float epsilon = (float)Double.Epsilon;
Trong cả hai ví dụ, ngay cả khi các giá trị nằm trong float
phạm vi, bạn sẽ mất thông tin (trong trường hợp này là độ chính xác), vì vậy chuyển đổi phải rõ ràng. Bây giờ hãy thử điều này:
float max = (float)Double.MaxValue;
Việc chuyển đổi này sẽ không thành công, vì vậy, một lần nữa, nó phải rõ ràng để bạn biết về nó và bạn có thể kiểm tra (trong ví dụ, giá trị là không đổi nhưng nó có thể đến từ một số tính toán thời gian chạy hoặc I / O). Quay lại ví dụ của bạn:
string text = "123";
double value = (double)text;
Điều này sẽ không biên dịch vì trình biên dịch không thể chuyển đổi văn bản thành số. Văn bản có thể chứa bất kỳ ký tự nào, không chỉ số và điều này là quá nhiều, trong C #, ngay cả đối với một kiểu truyền rõ ràng (nhưng nó có thể được cho phép ở ngôn ngữ khác).
Các đối tượng
Chuyển đổi từ con trỏ (thành đối tượng) có thể không thành công nếu các kiểu không liên quan, ví dụ: mã này sẽ không biên dịch (vì trình biên dịch biết không có khả năng chuyển đổi):
string text = (string)AppDomain.Current;
Exception exception = (Exception)"abc";
Mã này sẽ biên dịch nhưng nó có thể bị lỗi trong thời gian chạy (nó phụ thuộc vào loại hiệu quả của các đối tượng được truyền) với InvalidCastException
:
object obj = GetNextObjectFromInput();
string text = (string)obj;
obj = GetNextObjectFromInput();
Exception exception = (Exception)obj;
Chuyển đổi
Vì vậy, cuối cùng, nếu phôi là chuyển đổi thì tại sao chúng ta cần các lớp như thế Convert
? Bỏ qua những khác biệt nhỏ đến từ Convert
việc triển khai và IConvertible
triển khai thực sự bởi vì trong C # với một diễn viên bạn nói với trình biên dịch:
tin tôi đi, loại này là loại đó ngay cả khi bạn không thể biết nó bây giờ, hãy để tôi làm và bạn sẽ thấy.
-hoặc là-
đừng lo lắng, tôi không quan tâm nếu cái gì đó sẽ bị mất trong chuyển đổi này.
Đối với bất kỳ điều gì khác, một hoạt động rõ ràng hơn là cần thiết (hãy nghĩ về hàm ý của các phôi dễ dàng , đó là lý do tại sao C ++ giới thiệu cú pháp dài, dài dòng và rõ ràng cho chúng). Điều này có thể liên quan đến một hoạt động phức tạp (đối với string
-> double
chuyển đổi sẽ cần phân tích cú pháp). string
Ví dụ: chuyển đổi thành luôn có thể thực hiện được (thông qua ToString()
phương thức) nhưng nó có thể có ý nghĩa khác với những gì bạn mong đợi, vì vậy nó phải rõ ràng hơn là một diễn viên ( bạn viết nhiều hơn, bạn nghĩ nhiều hơn về những gì bạn đang làm ).
Việc chuyển đổi này có thể được thực hiện bên trong đối tượng (sử dụng các hướng dẫn IL đã biết cho đối tượng đó), sử dụng các toán tử chuyển đổi tùy chỉnh (được định nghĩa trong lớp để ép kiểu) hoặc các cơ chế phức tạp hơn ( TypeConverter
ví dụ: các hoặc phương thức lớp). Bạn không biết điều gì sẽ xảy ra để làm điều đó nhưng bạn biết rằng nó có thể thất bại (đó là lý do tại sao IMO khi có thể chuyển đổi được kiểm soát nhiều hơn, bạn nên sử dụng nó). Trong trường hợp của bạn, chuyển đổi chỉ đơn giản sẽ phân tích cú pháp string
để tạo ra double
:
double value = Double.Parse(aStringVariable);
Tất nhiên điều này có thể không thành công vì vậy nếu bạn làm điều đó, bạn nên luôn nắm bắt ngoại lệ nó có thể ném ( FormatException
). Ở đây lạc đề nhưng khi TryParse
có sẵn thì bạn nên sử dụng nó (vì về mặt ngữ nghĩa bạn nói nó có thể không phải là số và nó thậm chí còn nhanh hơn ... hỏng).
Các chuyển đổi trong .NET có thể đến từ rất nhiều nơi, truyền TypeConverter
ngầm / rõ ràng với các toán tử chuyển đổi do người dùng xác định, triển khai IConvertible
và các phương thức phân tích cú pháp (tôi đã quên điều gì đó?). Hãy xem MSDN để biết thêm chi tiết về chúng.
Để kết thúc câu trả lời dài này, chỉ cần vài từ về toán tử chuyển đổi do người dùng xác định. Nó chỉ là đường để cho lập trình viên sử dụng cast để chuyển đổi loại này sang loại khác. Đó là một phương thức bên trong một lớp (lớp sẽ được ép kiểu) có nội dung "này, nếu anh ấy / cô ấy muốn chuyển đổi kiểu này thành kiểu đó thì tôi có thể làm được". Ví dụ:
float? maybe = 10;
float sure1 = (float)maybe;
float sure2 = maybe.Value;
Trong trường hợp này, nó rõ ràng vì nó có thể không thành công nhưng điều này được để cho việc thực hiện (ngay cả khi có hướng dẫn về điều này). Hãy tưởng tượng bạn viết một lớp chuỗi tùy chỉnh như thế này:
EasyString text = "123";
double value = (string)text;
Trong quá trình triển khai, bạn có thể quyết định "làm cho cuộc sống của lập trình viên dễ dàng hơn" và để hiển thị chuyển đổi này thông qua một diễn viên (hãy nhớ rằng đó chỉ là một phím tắt để viết ít hơn). Một số ngôn ngữ thậm chí có thể cho phép điều này:
double value = "123";
Cho phép chuyển đổi ngầm sang bất kỳ loại nào (kiểm tra sẽ được thực hiện tại thời điểm chạy). Với các tùy chọn thích hợp, điều này có thể được thực hiện, ví dụ, trong VB.NET. Đó chỉ là một triết lý khác.
Tôi có thể làm gì với chúng?
Vì vậy, câu hỏi cuối cùng là khi nào bạn nên sử dụng cái này hay cái khác. Hãy xem khi nào bạn có thể sử dụng một dàn diễn viên rõ ràng:
- Chuyển đổi giữa các loại cơ sở.
- Chuyển đổi từ
object
bất kỳ loại nào khác (điều này có thể bao gồm cả việc mở hộp).
- Chuyển đổi từ lớp dẫn xuất sang lớp cơ sở (hoặc sang giao diện được triển khai).
- Chuyển đổi từ loại này sang loại khác thông qua toán tử chuyển đổi tùy chỉnh.
Chỉ có thể thực hiện chuyển đổi đầu tiên đối với những chuyển đổi Convert
khác mà bạn không có lựa chọn nào khác và bạn cần sử dụng diễn viên rõ ràng.
Hãy xem ngay bây giờ khi bạn có thể sử dụng Convert
:
- Chuyển đổi từ bất kỳ kiểu cơ sở nào sang kiểu cơ sở khác (với một số hạn chế, xem MSDN ).
- Chuyển đổi từ bất kỳ loại nào triển khai
IConvertible
sang bất kỳ loại nào khác (được hỗ trợ).
- Chuyển đổi từ / sang một
byte
mảng thành / từ một chuỗi.
Kết luận
IMO Convert
nên được sử dụng mỗi khi bạn biết một chuyển đổi có thể không thành công (do định dạng, vì phạm vi hoặc vì nó có thể không được hỗ trợ), ngay cả khi có thể thực hiện chuyển đổi tương tự với một phép truyền (trừ khi có sẵn thứ gì khác). Nó nói rõ ai sẽ đọc mã của bạn mục đích của bạn là gì và nó có thể thất bại (đơn giản hóa việc gỡ lỗi).
Đối với mọi thứ khác, bạn cần sử dụng cast, không có lựa chọn nào khác, nhưng nếu có một phương pháp khác tốt hơn thì tôi khuyên bạn nên sử dụng nó. Trong ví dụ của bạn, một chuyển đổi từ string
thành double
là một thứ gì đó (đặc biệt là nếu văn bản đến từ người dùng) rất thường xuyên sẽ không thành công, vì vậy bạn nên làm cho nó càng rõ ràng càng tốt (hơn nữa bạn có nhiều quyền kiểm soát hơn đối với nó), chẳng hạn như sử dụng một TryParse
phương pháp.
Chỉnh sửa: sự khác biệt giữa chúng là gì?
Theo câu hỏi được cập nhật và giữ nguyên những gì tôi đã viết trước đây (về thời điểm bạn có thể sử dụng cast so với khi bạn có thể / phải sử dụng Convert
) thì điểm cuối cùng cần làm rõ là liệu có sự khác biệt giữa chúng hay không (hơn nữa là cách Convert
sử dụng IConvertible
và IFormattable
giao diện để nó có thể thực hiện các hoạt động không được phép với phôi).
Câu trả lời ngắn gọn là có, họ cư xử khác nhau . Tôi thấy Convert
lớp giống như một lớp phương thức trợ giúp nên thường nó cung cấp một số lợi ích hoặc các hành vi hơi khác một chút. Ví dụ:
double real = 1.6;
int castedInteger = (int)real;
int convertedInteger = Convert.ToInt32(real);
Khá khác biệt, phải không? Việc ép kiểu cắt ngắn (đó là điều mà tất cả chúng ta mong đợi) nhưng Convert
thực hiện làm tròn đến số nguyên gần nhất (và điều này có thể không xảy ra nếu bạn không biết về nó). Mỗi phương pháp chuyển đổi đều có sự khác biệt nên không thể áp dụng một quy tắc chung và chúng phải được xem xét theo từng trường hợp ... 19 loại cơ sở để chuyển đổi thành mọi loại khác ... danh sách có thể khá dài, tốt hơn nhiều nên tham khảo trường hợp MSDN bởi trường hợp!