Tôi có nên tránh sử dụng int unsign trong C # không?


23

Gần đây tôi đã nghĩ về việc sử dụng các số nguyên không dấu trong C # (và tôi đoán có thể nói đối số tương tự về các "ngôn ngữ cấp cao" khác)

Khi cần một số nguyên, thông thường tôi không phải đối mặt với tình trạng tiến thoái lưỡng nan về kích thước của một số nguyên, một ví dụ sẽ là thuộc tính tuổi của lớp Người (nhưng câu hỏi không giới hạn ở các thuộc tính). Với ý nghĩ đó, theo như tôi có thể thấy, chỉ có một lợi thế của việc sử dụng số nguyên không dấu ("uint") so với số nguyên đã ký ("int") - dễ đọc. Nếu tôi muốn bày tỏ ý tưởng rằng một độ tuổi chỉ có thể tích cực, tôi có thể đạt được điều này bằng cách đặt loại tuổi thành uint.

Mặt khác, các tính toán trên các số nguyên không dấu có thể dẫn đến các lỗi thuộc mọi loại và điều đó gây khó khăn cho việc thực hiện các thao tác như trừ hai độ tuổi. (Tôi đọc đây là một trong những lý do Java bỏ qua các số nguyên không dấu)

Trong trường hợp của C #, tôi cũng có thể nghĩ rằng một mệnh đề bảo vệ trên setter sẽ là một giải pháp mang lại điều tốt nhất cho hai thế giới, nhưng, điều này sẽ không được áp dụng khi tôi ví dụ, một độ tuổi sẽ được chuyển sang một phương thức nào đó. Một cách giải quyết khác là xác định một lớp có tên là Age và có tuổi thuộc tính là thứ duy nhất ở đó, nhưng mẫu này sẽ khiến tôi tạo ra nhiều lớp và sẽ là một sự nhầm lẫn (các nhà phát triển khác sẽ không biết khi nào một đối tượng chỉ là một trình bao bọc và khi đó là một cái gì đó nhẹ nhàng hơn).

Một số thực tiễn tốt nhất chung về vấn đề này là gì? Làm thế nào tôi nên đối phó với loại kịch bản này?



1
Ngoài ra int unsign không tuân thủ CLS, có nghĩa là bạn không thể gọi API sử dụng chúng từ các ngôn ngữ .NET khác.
Nathan Cooper

2
@NathanCooper: ... "không thể gọi API sử dụng chúng từ một số ngôn ngữ khác". Siêu dữ liệu cho chúng được chuẩn hóa, vì vậy tất cả các ngôn ngữ .NET hỗ trợ các loại không dấu sẽ hoạt động tốt.
Ben Voigt

5
Để giải quyết ví dụ cụ thể của bạn, tôi sẽ không có một tài sản được gọi là Tuổi ở vị trí đầu tiên. Tôi có một tài sản được gọi là Sinh nhật hoặc CreationTime hoặc bất cứ điều gì, và tính tuổi từ đó.
Eric Lippert

2
"... Nhưng mô hình này sẽ khiến tôi tạo ra nhiều lớp và sẽ là một nguồn gây nhầm lẫn" thực sự đó là điều chính xác phải làm. Chỉ cần tìm kiếm các mẫu chống ám ảnh nguyên thủy khét tiếng .
Songo

Câu trả lời:


23

Các nhà thiết kế của .NET Framework đã chọn một số nguyên có chữ ký 32 bit làm "số mục đích chung" của họ vì nhiều lý do:

  1. Nó có thể xử lý các số âm, đặc biệt là -1 (mà Khung sử dụng để chỉ ra một điều kiện lỗi; đây là lý do tại sao một int đã ký được sử dụng ở mọi nơi cần lập chỉ mục, mặc dù các số âm không có ý nghĩa trong ngữ cảnh lập chỉ mục).
  2. Nó đủ lớn để phục vụ hầu hết các mục đích, trong khi đủ nhỏ để sử dụng kinh tế ở hầu hết mọi nơi.

Lý do để sử dụng ints không dấu là không thể đọc được; nó có khả năng để có được phép toán mà chỉ một số nguyên không dấu cung cấp.

Điều khoản bảo vệ, xác nhận hợp lệ và điều kiện tiên quyết hợp đồng là những cách hoàn toàn chấp nhận được để đảm bảo phạm vi số hợp lệ. Ít khi phạm vi số trong thế giới thực tương ứng chính xác với một số trong khoảng từ 0 đến 2 32 -1 (hoặc bất kỳ phạm vi số gốc nào thuộc loại số bạn đã chọn), do đó, sử dụng uintđể hạn chế hợp đồng giao diện của bạn với các số dương là loại bên cạnh mục đích chính; bên cạnh vấn đề chính.


2
Câu trả lời tốt đẹp! Ngoài ra, có thể có một số trường hợp trong đó một số nguyên không dấu thực sự có thể vô tình tạo ra nhiều lỗi hơn (mặc dù có thể phát hiện ra ngay lập tức, nhưng hơi khó hiểu) - hãy tưởng tượng vòng lặp ngược lại với bộ đếm int không dấu vì một số kích thước là một số nguyên: for (uint j=some_size-1; j >= 0; --j)- rất tiếc ( không chắc chắn nếu đây là một vấn đề trong C #)! Tôi đã tìm thấy vấn đề này trong mã trước đó khi cố gắng sử dụng int unsign ở phía C càng nhiều càng tốt - và cuối cùng chúng tôi đã thay đổi nó để chỉ ưu tiên intvà cuộc sống của chúng tôi cũng dễ dàng hơn với ít cảnh báo trình biên dịch hơn.

14
"Hiếm khi một phạm vi số trong thế giới thực tương ứng với một số trong khoảng từ 0 đến 2 ^ 32-1." Theo kinh nghiệm của tôi, nếu bạn sẽ cần một số lớn hơn 2 ^ 31, thì rất có thể bạn cũng sẽ cần những số lớn hơn 2 ^ 32, vì vậy bạn cũng có thể chuyển lên (ký) int64 tại điểm đó.
Mason Wheeler

3
@Panzercrisis: Điều đó hơi nghiêm trọng. Có lẽ sẽ chính xác hơn khi nói "Sử dụng inthầu hết thời gian vì đó là quy ước đã được thiết lập và đó là điều mà hầu hết mọi người sẽ mong đợi được sử dụng thường xuyên. Sử dụng uintkhi bạn yêu cầu các đặc tính của a uint." Hãy nhớ rằng, các nhà thiết kế Khung đã quyết định tuân theo quy ước này một cách rộng rãi, do đó bạn thậm chí không thể sử dụng uinttrong nhiều bối cảnh Khung (nó không tương thích với loại).
Robert Harvey

2
@Panzercrisis Nó có thể là một phrasing quá mạnh; nhưng tôi không chắc là tôi đã từng sử dụng các loại không dấu trong C # trừ khi tôi gọi xuống win32 apis (trong đó quy ước là các hằng số / cờ / vv không được ký).
Dan Neely

4
Nó thực sự là khá hiếm. Lần duy nhất tôi từng sử dụng số nguyên không dấu là trong các tình huống xoay vòng bit.
Robert Harvey

8

Nói chung, bạn phải luôn sử dụng loại dữ liệu cụ thể nhất cho dữ liệu của mình.

Ví dụ: nếu bạn đang sử dụng Entity Framework để lấy dữ liệu từ cơ sở dữ liệu, thì EF sẽ tự động sử dụng loại dữ liệu gần nhất với loại được sử dụng trong cơ sở dữ liệu.

Có hai vấn đề với điều này trong C #.
Đầu tiên, hầu hết các nhà phát triển C # chỉ sử dụng int, để đại diện cho toàn bộ số (trừ khi có lý do để sử dụng long). Điều này có nghĩa là các nhà phát triển khác sẽ không nghĩ sẽ kiểm tra loại dữ liệu, vì vậy họ sẽ nhận được các lỗi tràn được đề cập ở trên. Thứ hai, và vấn đề quan trọng hơn, đó là / là NET của toán tử số học ban đầu chỉ được hỗ trợ int, uint, long, ulong, float, đôi, và decimal*. Đây vẫn là trường hợp ngày hôm nay (xem phần 7.8.4 trong thông số ngôn ngữ C # 5.0 ). Bạn có thể tự kiểm tra điều này bằng cách sử dụng mã sau đây:

byte a, b;
a = 1;
b = 2;
var c = a - b;      //In visual studio, hover over "var" and the tip will indicate the data type, or you can get the value from cName below.
string cName = c.GetType().Namespace + '.' + c.GetType().Name;

Kết quả của chúng tôi byte- bytelà một int( System.Int32).

Hai vấn đề này đã dẫn đến thực tiễn "chỉ sử dụng int cho toàn bộ số" rất phổ biến.

Vì vậy, để trả lời câu hỏi của bạn, trong C # thường là một ý tưởng tốt để bám vào inttrừ khi:

  • Một trình tạo mã tự động đã sử dụng một giá trị khác (như Entity Framework).
  • Tất cả các nhà phát triển khác trong dự án đều biết rằng bạn đang sử dụng các loại dữ liệu ít phổ biến hơn (bao gồm một nhận xét chỉ ra rằng bạn đã sử dụng loại dữ liệu và tại sao).
  • Các loại dữ liệu ít phổ biến hơn thường được sử dụng trong dự án.
  • Chương trình yêu cầu lợi ích của loại dữ liệu ít phổ biến hơn (bạn có 100 triệu trong số này bạn cần giữ RAM, do đó, sự khác biệt giữa a bytevà an inthoặc inta longlà rất quan trọng hoặc sự khác biệt về số học của dấu không được đề cập).

Nếu bạn cần làm toán trên dữ liệu, hãy bám vào các loại phổ biến.
Hãy nhớ rằng, bạn có thể truyền từ loại này sang loại khác. Điều này có thể kém hiệu quả hơn từ quan điểm của CPU, vì vậy bạn có thể tốt hơn với một trong 7 loại phổ biến, nhưng nó là một tùy chọn nếu cần.

Số liệt kê ( enum) là một trong những trường hợp ngoại lệ cá nhân của tôi đối với các nguyên tắc trên. Nếu tôi chỉ có một vài tùy chọn, tôi sẽ chỉ định enum là byte hoặc ngắn. Nếu tôi cần bit cuối cùng trong enum được gắn cờ, tôi sẽ chỉ định loại là uintđể tôi có thể sử dụng hex để đặt giá trị cho cờ.

Nếu bạn sử dụng một thuộc tính có mã giới hạn giá trị, hãy chắc chắn giải thích trong thẻ tóm tắt những hạn chế nào ở đó và tại sao.

* Bí danh C # được sử dụng thay cho tên .NET như System.Int32đây là câu hỏi C #.

Lưu ý: đã có một blog hoặc bài viết từ các nhà phát triển .NET (mà tôi không thể tìm thấy), trong đó chỉ ra số lượng hạn chế của các hàm số học và một số lý do tại sao họ không lo lắng về nó. Theo tôi nhớ, họ chỉ ra rằng họ không có kế hoạch thêm hỗ trợ cho các loại dữ liệu khác.

Lưu ý: Java không hỗ trợ các kiểu dữ liệu không dấu và trước đây không hỗ trợ cho các số nguyên 8 hoặc 16 bit. Do nhiều nhà phát triển C # đến từ nền Java hoặc cần làm việc ở cả hai ngôn ngữ, nên các giới hạn của một ngôn ngữ đôi khi sẽ bị áp đặt giả tạo lên ngôn ngữ kia.


Nguyên tắc chung của tôi chỉ đơn giản là "sử dụng int, trừ khi bạn không thể".
PerryC

@PerryC Tôi tin rằng đó là quy ước phổ biến nhất. Quan điểm của câu trả lời của tôi là cung cấp một quy ước hoàn chỉnh hơn cho phép bạn sử dụng các tính năng ngôn ngữ.
Đã xem

6

Bạn chủ yếu cần nhận thức được hai điều: dữ liệu bạn đại diện và bất kỳ bước trung gian nào trong tính toán của bạn.

Nó chắc chắn có ý nghĩa khi có tuổi unsigned int, bởi vì chúng ta thường không xem xét độ tuổi tiêu cực. Nhưng sau đó, bạn đề cập đến việc trừ đi một tuổi từ một thời đại khác. Nếu chúng ta chỉ trừ một cách mù quáng một số nguyên từ một số nguyên khác, thì chắc chắn có thể kết thúc bằng một số âm, ngay cả khi trước đó chúng ta đã đồng ý rằng các độ tuổi âm không có ý nghĩa. Vì vậy, trong trường hợp này, bạn sẽ muốn tính toán của mình được thực hiện với một số nguyên đã ký.

Liên quan đến việc các giá trị không dấu có xấu hay không, tôi sẽ nói rằng đó là một khái quát lớn để nói rằng các giá trị không dấu là xấu. Java không có các giá trị không dấu, như bạn đã đề cập và nó liên tục làm tôi khó chịu. A bytecó thể có giá trị từ 0-255 hoặc 0x00-0xFF. Nhưng nếu bạn muốn khởi tạo một byte lớn hơn 127 (0x7F), bạn phải ghi nó dưới dạng số âm hoặc chuyển một số nguyên thành một byte. Bạn kết thúc với mã trông như thế này:

byte a = 0x80; // Won't compile!
byte b = (byte) 0x80;
byte c = -128; // Equal to b

Những điều trên làm tôi khó chịu đến tận cùng. Tôi không được phép có một byte có giá trị 197, mặc dù đó là một giá trị hoàn toàn hợp lệ đối với hầu hết những người lành mạnh giao dịch với byte. Tôi có thể truyền số nguyên hoặc tôi có thể tìm giá trị âm (197 == -59 trong trường hợp này). Cũng xem xét điều này:

byte a = 70;
byte b = 80;
byte c = a + b; // c == -106

Vì vậy, như bạn có thể thấy, việc thêm hai byte với các giá trị hợp lệ và kết thúc bằng một byte có giá trị hợp lệ, cuối cùng sẽ thay đổi dấu hiệu. Không chỉ vậy mà không rõ ràng ngay lập tức rằng 70 + 80 == -106. Về mặt kỹ thuật, đây là một lỗi tràn, nhưng trong suy nghĩ của tôi (với tư cách là một con người), một byte không nên tràn cho các giá trị dưới 0xFF. Khi tôi làm số học bit trên giấy, tôi không coi bit thứ 8 là bit dấu.

Tôi làm việc với rất nhiều số nguyên ở cấp độ bit và việc ký kết mọi thứ thường khiến mọi thứ trở nên ít trực quan hơn và khó xử lý hơn, bởi vì bạn phải nhớ rằng việc chuyển sang phải một số âm mang lại cho bạn số mới 1. Trong khi đó, dịch chuyển sang phải một số nguyên không dấu không bao giờ làm điều đó. Ví dụ:

signed byte b = 0b10000000;
b = b >> 1; // b == 0b1100 0000
b = b & 0x7F;// b == 0b0100 0000

unsigned byte b = 0b10000000;
b = b >> 1; // b == 0b0100 0000;

Nó chỉ thêm các bước bổ sung mà tôi cảm thấy không cần thiết.

Trong khi tôi đã sử dụng byteở trên, điều tương tự áp dụng cho các số nguyên 32 bit và 64 bit. Không có unsignedlàm tê liệt và điều đó làm tôi sốc rằng có những ngôn ngữ cấp cao như Java hoàn toàn không cho phép chúng. Nhưng đối với hầu hết mọi người, đây không phải là vấn đề, bởi vì nhiều lập trình viên không giải quyết số học cấp độ bit.

Cuối cùng, thật hữu ích khi sử dụng các số nguyên không dấu nếu bạn nghĩ về chúng như các bit và việc sử dụng các số nguyên có chữ ký là rất hữu ích khi bạn nghĩ chúng là số.


7
Tôi chia sẻ sự thất vọng của bạn về các ngôn ngữ không có các loại tích phân không dấu (đặc biệt là các byte) nhưng tôi sợ rằng đây không phải là câu trả lời trực tiếp cho câu hỏi được hỏi ở đây. Có lẽ bạn có thể thêm một kết luận, mà tôi tin rằng, có thể là: “Sử dụng số nguyên unsigned nếu bạn đang nghĩ đến việc giá trị của họ như bit và ký số nguyên nếu bạn đang nghĩ về họ như những con số.”
5gon12eder

1
đó là những gì tôi đã nói trong một bình luận ở trên. vui mừng khi thấy người khác nghĩ giống như vậy.
robert bristow-johnson
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.