2012-01-10 14 views
6

Có lý do kỹ thuật nào không có chuyển đổi ngầm từ DBNull thành các loại null và/hoặc sql khác nhau không? Tôi hiểu tại sao các chuyển đổi hiện không xảy ra, nhưng không hiểu tại sao một chuyển đổi tiềm ẩn không được tạo tại thời điểm đó hoặc được thêm vào trong các phiên bản tiếp theo của khung.Có lý do kỹ thuật nào để không chuyển đổi ngầm từ DBNull thành các loại không có giá trị không?

Chỉ cần rõ ràng, tôi đang tìm lý do kỹ thuật, không phải "vì đó là cách họ đã làm" hoặc "Tôi thích nó theo cách đó".

+0

@DanielPryden: Ok, vậy bạn muốn được unboxing nó thành một giá trị khác nhau tùy thuộc vào nếu mục tiêu là một giá trị hoặc tham khảo loại? Đó có phải là ý bạn không? – jmoreno

+0

@DanielPryden: Câu hỏi đã được mở lại, hy vọng lần này nó sẽ mở đủ lâu để bạn có thể nhập câu trả lời. – jmoreno

+0

OK, tôi đã viết lại câu trả lời của mình, lần này với các tham chiếu đến spec. Hóa ra nó thực sự tồi tệ hơn tôi nghĩ: tại thời điểm này, tôi khá thuyết phục rằng nó không chỉ là một ý tưởng tồi, nó hoàn toàn không thể. Hy vọng câu trả lời của tôi đáng để chờ đợi! –

Trả lời

3

Vâng, tôi không biết gì về trường hợp SqlTypes, nhưng chắc chắn có một số lý do tại sao kỹ thuật thêm một chuyển đổi ngầm giữa DBNull.Value và giá trị của Nullable<T> với HasValue = false sẽ không hoạt động.

Hãy nhớ rằng, DBNull là một loại tài liệu tham khảo, và mặc dù thực tế rằng Nullable<T>hành vi như một loại tài liệu tham khảo - bằng cách giả vờ để có thể đưa vào giá trị null - nó thực sự là một kiểu giá trị, với ngữ nghĩa giá trị.

Cụ thể, có trường hợp cạnh kỳ lạ khi các giá trị loại Nullable<T> được đóng hộp. Hành vi này được đặt biệt trong thời gian chạy tới các giá trị hộp của loại Nullable<T> đến phiên bản được đóng hộp là T, không phải là phiên bản được đóng hộp là Nullable<T>.

Như the MSDN documentation giải thích nó:

Khi một loại nullable được đóng hộp, bộ thực thi ngôn ngữ chung tự động hộp giá trị cơ bản của Nullable (Of T) đối tượng, không phải là Nullable (Of T) đối tượng riêng của mình. Nghĩa là, nếu thuộc tính HasValue là true, nội dung của thuộc tính Value được đóng hộp. Khi giá trị cơ bản của một kiểu nullable là unboxed, thời gian chạy ngôn ngữ chung tạo ra một cấu trúc Nullable (Of T) mới được khởi tạo với giá trị cơ bản.

Nếu thuộc tính HasValue của loại có thể vô hiệu là sai, kết quả của thao tác boxing là Không có gì. Do đó, nếu một kiểu nullable được đóng hộp được chuyển tới một phương thức mong đợi đối số đối tượng, thì phương thức đó phải được chuẩn bị để xử lý trường hợp đối số là Không có gì. Khi không có gì được unboxed vào một loại nullable, thời gian chạy ngôn ngữ chung tạo ra một cấu trúc Nullable (Of T) mới và khởi tạo tài sản HasValue của nó thành false.

Bây giờ chúng ta đi vào một vấn đề hóc búa: spec ngôn ngữ C# (§ 4.3.2) nói rằng chúng ta không thể sử dụng một chuyển đổi unboxing để chuyển đổi DBNull.Value vào Nullable<T>:

Đối với một chuyển đổi unboxing để một loại không thể loại để thành công tại thời gian chạy, giá trị của toán hạng nguồn phải là giá trị rỗng hoặc tham chiếu đến giá trị được đóng hộp của không thể nhập giá trị của không thể loại .Nếu toán hạng nguồn là một tham chiếu đến một đối tượng không tương thích, một số System.InvalidCastException sẽ bị ném.

Và chúng ta không thể sử dụng một chuyển đổi người dùng định nghĩa để chuyển đổi từ object để Nullable<T>, một trong hai, theo § 10.10.3:

Nó không thể trực tiếp xác định lại một được xác định trước chuyển đổi. Do đó, nhà khai thác chuyển đổi không được phép chuyển đổi từ hoặc sang số object vì chuyển đổi ngầm và rõ ràng đã tồn tại giữa object và tất cả các loại khác.

OK, bạn hoặc tôi không thể làm điều đó, nhưng Microsoft chỉ có thể sửa đổi thông số và làm cho nó hợp pháp, đúng không? Tôi không nghĩ vậy.

Tại sao? Hãy tưởng tượng trường hợp sử dụng dự định: bạn đã có một số phương thức được chỉ định để trả về object. Trong thực tế, nó trả về DBNull.Value hoặc int. Nhưng làm thế nào trình biên dịch có thể biết điều đó? Tất cả những gì nó biết là phương thức được chỉ định để trả về object. Và toán tử chuyển đổi được áp dụng phải được chọn tại thời gian biên dịch.

OK, do đó, giả sử rằng có một toán tử phép thuật có thể chuyển đổi từ object thành Nullable<T> và trình biên dịch có một số cách để biết khi nào nó có thể áp dụng. (Chúng tôi không muốn nó được sử dụng cho mỗi phương pháp được chỉ định để trả lại object - bạn nên làm gì nếu phương pháp thực sự trả về một string?) Nhưng chúng tôi vẫn gặp sự cố: chuyển đổi có thể không rõ ràng! Nếu phương thức trả về hoặc là long hoặc DBNull.Value và chúng tôi làm int? v = Method();, chúng tôi nên làm gì khi phương thức trả về một ô được đóng hộp long?

Về cơ bản, để thực hiện công việc này như dự định, bạn phải sử dụng tương đương dynamic để xác định loại khi chạy và chuyển đổi dựa trên loại thời gian chạy. Nhưng sau đó chúng tôi đã phá vỡ một quy tắc khác: vì chuyển đổi thực sự sẽ chỉ được chọn khi chạy, không có gì đảm bảo rằng nó thực sự thành công. Nhưng tiềm ẩn chuyển đổi không được phép ném ngoại lệ.

Vì vậy, tại thời điểm này, nó không chỉ là một thay đổi đối với hành vi được chỉ định của ngôn ngữ, một hiệu suất tiềm năng đáng kể, và trên hết nó có thể ném một ngoại lệ bất ngờ! Điều đó có vẻ như một lý do khá tốt để không thực hiện nó. Nhưng nếu bạn cần thêm một lý do nữa, hãy nhớ rằng mọi tính năng đều bắt đầu từ minus 100 points.

Tóm lại: những gì bạn thực sự muốn ở đây không thể thực hiện được bằng chuyển đổi ngầm. Nếu bạn muốn hành vi của dynamic, chỉ cần sử dụng dynamic! Đây không những gì bạn muốn, và đã được thực hiện trong C# 4.0:

object x = 23; 
object y = null; 

dynamic dx = x; 
dynamic dy = y; 

int? nx = (int?) dx; 
int? ny = (int?) dy; 

Console.WriteLine("nx.HasValue = {0}; nx.Value = {1}; ny.HasValue = {2};", 
    nx.HasValue, nx.Value, ny.HasValue); 
+1

Vâng, điều này đáng để chờ đợi. Một caveat nhỏ, C# null coalescing nhà điều hành thực sự làm cho việc chuyển đổi khá đơn giản (thông qua một cơ chế khác nhau, đó là lý do tại sao tôi đã hỏi về khuôn khổ). int? i = x là int? ?? vô giá trị; Điều này là cụ thể cho trình biên dịch C# và ?? toán tử, và mặc dù bất kỳ ngôn ngữ nào có thể thêm tương đương, nó không phải là vốn có trong CLR (đó là điều tôi quan tâm). – jmoreno

+0

@jmoreno: Hmm ... đó là một mẹo thông minh với các toán tử 'as' và' ?? ', tôi đã không nghĩ đến điều đó. Điều đó có thể sẽ hoạt động hiệu quả hơn nhiều so với 'dynamic', mặc dù tôi vẫn không nghĩ rằng bạn có thể làm điều đó như một toán tử chuyển đổi * ngầm định, vì bạn sẽ cần phải biết loại giá trị thích hợp. Bạn có thể làm điều đó với một toán tử chuyển đổi * rõ ràng *, mặc dù bạn có thể muốn thử phương thức mở rộng thay thế, điều này có thể dễ dàng hơn để làm việc. –

+0

đúng, tôi không nghĩ rằng bạn có thể làm điều đó với một nhà điều hành ngầm nữa. Trong C# bạn có thể viết một phương thức mở rộng, bạn không thể làm điều đó trong VB vì nó không cho phép bạn làm như vậy đối với Object vì có thể xung đột với ràng buộc trễ. Tôi có một chức năng tiện ích chung cho VB, hoạt động. – jmoreno

7

Lời giải thích đơn giản nhất đến từ Microsoft's documentation:

CLR loại nullable không dành cho lưu trữ của null cơ sở dữ liệu vì một null ANSI SQL không hành xử theo cách tương tự như một tham chiếu null (hoặc có gì trong Visual Căn bản).

+5

Nói cách khác, chúng tôi cố tình không làm điều này bởi vì chúng tôi muốn bạn nhận thức đầy đủ về những gì bạn đang làm. – NotMe

+0

@competent_tech: Câu tiếp theo từ đó là "Để làm việc với các giá trị null ANSI SQL của cơ sở dữ liệu, hãy sử dụng System.Data.SqlTypes nulls thay vì Nullable.", Nhưng ... "Giá trị của loại 'System.DBNull' không thể được chuyển đổi để 'System.Data.SqlTypes.SqlInt32' " – jmoreno

+0

@jmoreno: Tôi nghĩ rằng điều này được giải quyết xa hơn một chút dưới gán giá trị null:' Nulls trong System.Data.SqlTypes là loại cụ thể và không thể được đại diện bởi một giá trị duy nhất , chẳng hạn như DbNull. Sử dụng thuộc tính IsNull để kiểm tra null.' –

Các vấn đề liên quan