ฉันควรหลีกเลี่ยงการใช้ int ที่ไม่ได้ลงชื่อใน C # หรือไม่


23

ฉันเพิ่งคิดเกี่ยวกับการใช้จำนวนเต็มไม่ได้ลงนามใน C # (และฉันคิดว่าอาร์กิวเมนต์ที่คล้ายกันสามารถพูดเกี่ยวกับ "ภาษาระดับสูงอื่น ๆ ")

เมื่อต้องการจำนวนเต็มปกติแล้วฉันไม่ต้องเผชิญกับภาวะที่กลืนไม่เข้าคายไม่ออกของขนาดของจำนวนเต็มตัวอย่างจะเป็นคุณสมบัติอายุของคลาสบุคคล (แต่คำถามไม่ได้ จำกัด เฉพาะคุณสมบัติ) โดยที่ในใจมีเท่าที่ฉันเห็นเพียงหนึ่งข้อได้เปรียบของการใช้จำนวนเต็มไม่ได้ลงนาม ("uint") มากกว่าจำนวนเต็มลงนาม ("int") - สามารถอ่านได้ หากฉันต้องการแสดงความคิดเห็นว่าอายุเป็นบวกได้ฉันสามารถบรรลุสิ่งนี้ได้โดยการตั้งค่าประเภทอายุเป็น uint

ในทางกลับกันการคำนวณจำนวนเต็มที่ไม่ได้ลงชื่อสามารถนำไปสู่ข้อผิดพลาดทุกประเภทและทำให้การดำเนินการเช่นการลบสองอายุทำได้ยาก (ฉันอ่านนี่เป็นหนึ่งในเหตุผลที่ Java ละเว้นจำนวนเต็มที่ไม่ได้ลงชื่อ)

ในกรณีของ C # ฉันยังสามารถคิดได้ว่าประโยคของผู้พิทักษ์จะเป็นทางออกที่ให้สิ่งที่ดีที่สุดในโลกทั้งสอง แต่สิ่งนี้จะไม่สามารถใช้ได้เมื่อฉันยกตัวอย่างเช่นอายุจะผ่านไปยังวิธีการบางอย่าง วิธีแก้ปัญหาคือการกำหนดคลาสที่เรียกว่า Age และให้อายุคุณสมบัติเป็นสิ่งเดียวที่มี แต่รูปแบบนี้จะให้ฉันสร้างคลาสจำนวนมากและจะเป็นแหล่งที่มาของความสับสน (นักพัฒนาอื่น ๆ จะไม่ทราบเมื่อวัตถุเป็นเพียงเสื้อคลุม และเมื่อมันมีอะไรบางอย่างที่ทำให้สับสนมากขึ้น)

แนวปฏิบัติทั่วไปที่ดีที่สุดเกี่ยวกับปัญหานี้มีอะไรบ้าง ฉันจะจัดการกับสถานการณ์ประเภทนี้ได้อย่างไร



1
นอกจากนี้ int ที่ไม่ได้ลงนามไม่สอดคล้องกับ CLS ซึ่งหมายความว่าคุณไม่สามารถเรียก API ที่ใช้พวกเขาจากภาษา. NET อื่น ๆ
นาธานคูเปอร์

2
@NathanCooper: ... "ไม่สามารถเรียกใช้ API ที่ใช้พวกเขาจากบางส่วนภาษาอื่น ๆ" ข้อมูลเมตาสำหรับพวกเขาเป็นมาตรฐานดังนั้นภาษา. NET ทั้งหมดที่รองรับประเภทที่ไม่ได้ลงนามจะทำงานร่วมกันได้ดี
Ben Voigt

5
เพื่อระบุตัวอย่างเฉพาะของคุณฉันจะไม่มีสถานที่ให้บริการที่เรียกว่าอายุในสถานที่แรก ฉันมีสถานที่ให้บริการที่เรียกว่าวันเกิดหรือเวลา CreationTime หรืออะไรก็ตามและคำนวณอายุจากมัน
Eric Lippert

2
"... แต่รูปแบบนี้จะให้ฉันสร้างคลาสจำนวนมากและอาจเป็นแหล่งของความสับสน"จริง ๆ แล้วนั่นเป็นสิ่งที่ถูกต้อง เพียงแค่ค้นหารูปแบบการต่อต้านPrimitive Obsession ที่น่าอับอาย
Songo

คำตอบ:


24

ผู้ออกแบบของ. NET Framework เลือกจำนวนเต็มที่มีลายเซ็น 32 บิตเป็น "หมายเลขวัตถุประสงค์ทั่วไป" ด้วยเหตุผลหลายประการ:

  1. มันสามารถจัดการกับจำนวนลบโดยเฉพาะอย่างยิ่ง -1 (ซึ่ง Framework ใช้เพื่อระบุเงื่อนไขข้อผิดพลาดนี่คือเหตุผลที่ต้องใช้ int ที่ลงนามแล้วทุกที่ที่ต้องการการสร้างดัชนีแม้ว่าจำนวนลบจะไม่มีความหมายในบริบทการทำดัชนี)
  2. มันใหญ่พอที่จะให้บริการตามวัตถุประสงค์ส่วนใหญ่ในขณะที่มีขนาดเล็กพอที่จะใช้ในเชิงเศรษฐกิจได้เกือบทุกที่

เหตุผลในการใช้ int ที่ไม่ได้ลงชื่อไม่สามารถอ่านได้ มันมีความสามารถในการรับคณิตศาสตร์ที่ int เท่านั้นที่ไม่ได้ลงนามให้

ส่วนคำสั่งการตรวจสอบและเงื่อนไขสัญญาเป็นวิธีที่ยอมรับได้อย่างสมบูรณ์เพื่อประกันช่วงตัวเลขที่ถูกต้อง Seldom ทำช่วงตัวเลขในโลกแห่งความจริงตรงกับตัวเลขระหว่างศูนย์ถึง 2 32 -1 (หรือช่วงตัวเลขดั้งเดิมใด ๆ ที่เป็นประเภทตัวเลขที่คุณเลือก) ดังนั้นการใช้ a uintเพื่อ จำกัด สัญญาอินเทอร์เฟซของคุณให้เป็นจำนวนบวกคือ ข้างจุด


2
คำตอบที่ดี! นอกจากนี้อาจมีบางกรณีที่ int ที่ไม่ได้ลงชื่ออาจทำให้เกิดข้อผิดพลาดมากขึ้น (แม้ว่าอาจจะเห็นได้ทันที แต่สับสนเล็กน้อย) - ลองวนรอบกลับกับตัวนับ int ที่ไม่ได้ลงชื่อเนื่องจากขนาดบางขนาดเป็นจำนวนเต็ม: for (uint j=some_size-1; j >= 0; --j)- whoops ( ไม่แน่ใจว่านี่เป็นปัญหาใน C #) หรือไม่! ฉันพบปัญหานี้ในรหัสก่อนที่จะพยายามใช้ int ที่ไม่ได้ลงชื่อในฝั่ง C มากที่สุดเท่าที่จะเป็นไปได้ - และเราก็จบลงด้วยการเปลี่ยนให้เป็นที่นิยมในintภายหลังและชีวิตของเราก็ง่ายขึ้นมาก

14
"ไม่ค่อยมีช่วงตัวเลขจริงในโลกตรงกับตัวเลขระหว่างศูนย์ถึง 2 ^ 32-1" จากประสบการณ์ของฉันถ้าคุณต้องการจำนวนที่มากกว่า 2 ^ 31 คุณมีโอกาสมากที่จะต้องจบลงด้วยจำนวนที่มากกว่า 2 ^ 32 ดังนั้นคุณอาจเพิ่งย้ายไปที่ (ลงนาม) int64 ที่ จุดนั้น
Mason Wheeler

3
@Panercrisis: มันค่อนข้างรุนแรง มันอาจจะแม่นยำมากกว่าที่จะพูดว่า "ใช้intเวลาส่วนใหญ่เพราะเป็นแบบแผนที่จัดตั้งขึ้นและเป็นสิ่งที่คนส่วนใหญ่คาดว่าจะเห็นการใช้งานเป็นประจำใช้uintเมื่อคุณต้องการความสามารถพิเศษของ a uint" โปรดจำไว้ว่าผู้ออกแบบ Framework ตัดสินใจที่จะปฏิบัติตามอนุสัญญานี้อย่างกว้างขวางดังนั้นคุณจึงไม่สามารถใช้งานได้uintในบริบทของ Framework จำนวนมาก (ไม่รองรับการใช้งานประเภท)
Robert Harvey

2
@PanzCrisis มันอาจจะเป็นประโยคที่แข็งแกร่งมากเกินไป แต่ฉันไม่แน่ใจว่าฉันเคยใช้ประเภทที่ไม่ได้ลงชื่อใน C # ยกเว้นเมื่อฉันถูกเรียกไปที่ win32 apis (โดยที่การประชุมคือค่าคงที่ / ธง / ฯลฯ จะไม่ได้ลงนาม)
Dan Neely

4
มันค่อนข้างหายากแน่นอน ครั้งเดียวที่ฉันเคยใช้ ints ที่ไม่ได้ลงชื่อนั้นอยู่ในสถานการณ์บิตสองตอน
Robert Harvey

8

โดยทั่วไปคุณควรใช้ประเภทข้อมูลเฉพาะที่สุดสำหรับข้อมูลของคุณเท่าที่จะทำได้

ตัวอย่างเช่นหากคุณใช้ Entity Framework เพื่อดึงข้อมูลจากฐานข้อมูล EF จะใช้ชนิดข้อมูลที่ใกล้เคียงที่สุดกับที่ใช้ในฐานข้อมูลโดยอัตโนมัติ

มีปัญหาสองข้อใน C #
ก่อนอื่นนักพัฒนา C # ส่วนใหญ่จะใช้intเพื่อแสดงตัวเลขทั้งหมด (เว้นแต่มีเหตุผลที่จะใช้long) ซึ่งหมายความว่าผู้พัฒนารายอื่นจะไม่คิดว่าจะตรวจสอบชนิดข้อมูลดังนั้นพวกเขาจะได้รับข้อผิดพลาดมากเกินที่กล่าวไว้ข้างต้น ที่สองและปัญหาที่สำคัญมากขึ้นเป็น / คือว่า .NET ของผู้ประกอบการทางคณิตศาสตร์เดิมสนับสนุนเฉพาะint, uint, long, ulong, float, คู่และdecimal* ยังคงเป็นกรณีนี้ในวันนี้ (ดูหัวข้อ 7.8.4 ในข้อกำหนดภาษา C # 5.0 ) คุณสามารถทดสอบได้ด้วยตัวเองโดยใช้รหัสต่อไปนี้:

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;

ผลลัพธ์ของเราbyte- byteคือint( System.Int32)

ปัญหาทั้งสองนี้ก่อให้เกิดการฝึก "ใช้ int สำหรับตัวเลขทั้งหมดเท่านั้น" ซึ่งเป็นเรื่องธรรมดา

ดังนั้นเพื่อตอบคำถามของคุณใน C # เป็นความคิดที่ดีintหากไม่ปฏิบัติตาม:

  • เครื่องสร้างรหัสอัตโนมัติใช้ค่าที่แตกต่าง (เช่น Entity Framework)
  • นักพัฒนาอื่น ๆ ทั้งหมดในโครงการทราบว่าคุณกำลังใช้ชนิดข้อมูลที่น้อยกว่าปกติ (รวมถึงความคิดเห็นที่ชี้ให้เห็นว่าคุณใช้ประเภทข้อมูลและสาเหตุ)
  • ชนิดข้อมูลที่ใช้กันทั่วไปน้อยกว่าที่ใช้กันทั่วไปในโครงการแล้ว
  • โปรแกรมต้องการประโยชน์ของชนิดข้อมูลที่พบได้น้อยกว่า (คุณมี 100 ล้านของสิ่งเหล่านี้คุณต้องเก็บไว้ใน RAM ดังนั้นความแตกต่างระหว่าง a byteและa intหรือinta และ a longเป็นสิ่งสำคัญหรือความแตกต่างทางคณิตศาสตร์

หากคุณต้องการทำคณิตศาสตร์กับข้อมูลให้ยึดติดกับประเภททั่วไป
จำไว้ว่าคุณสามารถส่งจากประเภทหนึ่งไปอีกประเภทหนึ่ง สิ่งนี้อาจมีประสิทธิภาพน้อยกว่าจากจุดยืนของ CPU ดังนั้นคุณน่าจะดีกว่าด้วยหนึ่งใน 7 ประเภททั่วไป แต่เป็นตัวเลือกถ้าจำเป็น

การแจกแจง ( enum) เป็นหนึ่งในข้อยกเว้นส่วนตัวของฉันสำหรับแนวทางข้างต้น หากฉันมีเพียงไม่กี่ตัวเลือกฉันจะระบุ enumเป็น byte หรือ short ถ้าฉันต้องการบิตสุดท้ายใน enum ที่ถูกตั้งค่าสถานะฉันจะระบุชนิดให้เป็นuintดังนั้นฉันสามารถใช้ hex เพื่อตั้งค่าสำหรับการตั้งค่าสถานะ

หากคุณใช้พร็อพเพอร์ตี้ที่มีรหัสการ จำกัด ค่าควรแน่ใจว่าได้อธิบายไว้ในแท็กสรุปว่ามีข้อ จำกัด อะไรและทำไม

* ใช้ชื่อแทน C # แทนชื่อ. NET เช่นSystem.Int32นี้เนื่องจากเป็นคำถาม C #

หมายเหตุ: มีบล็อกหรือบทความจากผู้พัฒนา. NET (ซึ่งฉันหาไม่ได้) ซึ่งชี้ให้เห็นจำนวนฟังก์ชันทางคณิตศาสตร์ที่ จำกัด และเหตุผลบางประการที่ทำให้พวกเขาไม่ต้องกังวล อย่างที่ฉันจำได้พวกเขาระบุว่าพวกเขาไม่มีแผนที่จะเพิ่มการสนับสนุนสำหรับประเภทข้อมูลอื่น

หมายเหตุ: Java ไม่รองรับชนิดข้อมูลที่ไม่ได้ลงชื่อและก่อนหน้านี้ไม่รองรับตัวเลขทั้งหมด 8 หรือ 16 บิต เนื่องจากนักพัฒนา C # หลายคนมาจากพื้นหลังของจาวาหรือจำเป็นต้องทำงานในทั้งสองภาษาข้อ จำกัด ของภาษาหนึ่งจึงถูกกำหนดไว้ในบางครั้ง


กฎทั่วไปของฉันง่ายๆคือ "ใช้ int เว้นแต่คุณจะทำไม่ได้"
PerryC

@PerryC ฉันเชื่อว่านี่เป็นเรื่องธรรมดาที่สุด ประเด็นของคำตอบของฉันคือการจัดให้มีการประชุมที่สมบูรณ์มากขึ้นซึ่งช่วยให้คุณใช้คุณสมบัติภาษา
เดินขบวน

6

คุณต้องตระหนักถึงสองสิ่งหลัก ๆ คือข้อมูลที่คุณเป็นตัวแทนและขั้นตอนกลางในการคำนวณของคุณ

มันสมเหตุสมผลแล้วที่จะมีอายุunsigned intเพราะเรามักไม่คำนึงถึงวัยที่ไม่ดี แต่คุณพูดถึงการลบอายุหนึ่งจากอีกอันหนึ่ง หากเราแค่ลบจำนวนเต็มหนึ่งตัวจากที่อื่นอย่างสุ่ม ๆ มันก็เป็นไปได้ที่จะจบลงด้วยจำนวนลบแม้ว่าก่อนหน้านี้เราจะเห็นด้วยว่าอายุเชิงลบไม่สมเหตุสมผล ดังนั้นในกรณีนี้คุณต้องการให้การคำนวณของคุณเสร็จสมบูรณ์ด้วยจำนวนเต็มที่ลงนามแล้ว

ในเรื่องที่เกี่ยวกับว่าค่าที่ไม่ได้ลงนามนั้นไม่ดีหรือไม่ฉันจะบอกว่ามันเป็นลักษณะทั่วไปขนาดใหญ่ที่จะบอกว่าค่าที่ไม่ได้ลงชื่อนั้นไม่ดี Java ไม่มีค่าที่ไม่ได้ลงนามตามที่คุณพูดถึงและมันทำให้ฉันรำคาญอยู่ตลอดเวลา A byteสามารถมีค่าได้ตั้งแต่ 0-255 หรือ 0x00-0xFF แต่ถ้าคุณต้องการยกตัวอย่างไบต์ที่มีขนาดใหญ่กว่า 127 (0x7F) คุณต้องเขียนมันเป็นจำนวนลบหรือแปลงจำนวนเต็มไปเป็นไบต์ คุณท้ายด้วยรหัสที่มีลักษณะเช่นนี้:

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

เสียงดังกล่าวทำให้ฉันรำคาญไม่รู้จบ ฉันไม่ได้รับอนุญาตให้มี byte มีค่า 197 แม้ว่าจะเป็นค่าที่ถูกต้องสมบูรณ์แบบสำหรับคนที่มีเหตุผลส่วนใหญ่ที่เกี่ยวข้องกับ bytes ฉันสามารถเลือกจำนวนเต็มหรือหาค่าลบ (197 == -59 ในกรณีนี้) พิจารณาสิ่งนี้ด้วย:

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

ดังนั้นอย่างที่คุณเห็นการเพิ่มสองไบต์ด้วยค่าที่ถูกต้องและลงท้ายด้วยไบต์ด้วยค่าที่ถูกต้องท้ายที่สุดการเปลี่ยนเครื่องหมาย ไม่เพียงแค่นั้น แต่ไม่ชัดเจนทันทีว่า 70 + 80 == -106 ในทางเทคนิคนี่คือการล้น แต่ในใจของฉัน (เป็นมนุษย์) ไบต์ไม่ควรล้นสำหรับค่าภายใต้ 0xFF เมื่อฉันคำนวณเลขคณิตบนกระดาษฉันไม่คิดว่าบิตที่ 8 เป็นบิตสัญญาณ

ผมทำงานที่มีจำนวนมากของจำนวนเต็มในระดับบิตและมีทุกอย่างที่จะลงนามมักจะทำให้ทุกอย่างง่ายน้อยและยากที่จะจัดการกับเพราะคุณต้องจำไว้ว่าขวาขยับจำนวนลบจะช่วยให้คุณใหม่1ในหมายเลขของคุณ ในขณะที่การเลื่อนไปทางขวาจำนวนเต็มที่ไม่ได้ลงชื่อไม่เคยทำเช่นนั้น ตัวอย่างเช่น:

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;

มันเพิ่มขั้นตอนพิเศษที่ฉันรู้สึกว่าไม่จำเป็น

ในขณะที่ฉันใช้byteข้างต้นเช่นเดียวกับจำนวนเต็ม 32- บิตและ 64- บิต ไม่มีความunsignedพิการและมันทำให้ฉันตกใจว่ามีภาษาระดับสูงอย่าง Java ที่ไม่อนุญาตเลย แต่สำหรับคนส่วนใหญ่นี่ไม่ใช่ปัญหาเพราะโปรแกรมเมอร์หลายคนไม่จัดการกับเลขคณิตระดับบิต

ในท้ายที่สุดมันมีประโยชน์ที่จะใช้จำนวนเต็มที่ไม่ได้ลงนามหากคุณคิดว่ามันเป็นบิตและเป็นประโยชน์ในการใช้จำนวนเต็มที่ลงนามเมื่อคุณคิดว่าพวกเขาเป็นตัวเลข


7
ฉันแบ่งปันความคับข้องใจของคุณเกี่ยวกับภาษาที่ไม่มีอินทิกรัลประเภทที่ไม่ได้ลงนาม (โดยเฉพาะอย่างยิ่งสำหรับไบต์) แต่ฉันเกรงว่านี่ไม่ใช่คำตอบที่ตรงกับคำถามที่ถามที่นี่ บางทีคุณอาจเพิ่มข้อสรุปซึ่งฉันเชื่อว่าอาจเป็น:“ ใช้จำนวนเต็มที่ไม่ได้ลงนามหากคุณกำลังคิดว่าค่าของพวกเขาเป็นบิตและจำนวนเต็มที่ลงนามหากคุณคิดว่าพวกเขาเป็นตัวเลข”
5gon12eder

1
มันเป็นสิ่งที่ฉันพูดในความคิดเห็นด้านบน ดีใจที่เห็นคนอื่นคิดแบบเดียวกัน
เบิร์ตบริสโตจอห์นสัน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.