ทำไมฉันไม่สามารถกำหนดนวกรรมิกเริ่มต้นสำหรับ struct ใน. NET ได้?


261

ใน. NET ประเภทค่า (C # struct) ไม่สามารถมีตัวสร้างที่ไม่มีพารามิเตอร์ ตามโพสต์นี้ได้รับคำสั่งจากข้อกำหนดของ CLI สิ่งที่เกิดขึ้นคือสำหรับทุกประเภทค่าตัวสร้างเริ่มต้นจะถูกสร้างขึ้น (โดยคอมไพเลอร์?) ซึ่งเริ่มต้นสมาชิกทั้งหมดให้เป็นศูนย์ (หรือnull)

ทำไมมันไม่อนุญาตให้กำหนดคอนสตรัคเตอร์เริ่มต้นเช่นนี้

การใช้สิ่งเล็กน้อยสำหรับตัวเลขที่มีเหตุผล:

public struct Rational {
    private long numerator;
    private long denominator;

    public Rational(long num, long denom)
    { /* Todo: Find GCD etc. */ }

    public Rational(long num)
    {
        numerator = num;
        denominator = 1;
    }

    public Rational() // This is not allowed
    {
        numerator = 0;
        denominator = 1;
    }
}

การใช้เวอร์ชันปัจจุบันของ C # ค่า Rational ที่เป็นค่าเริ่มต้น0/0จะไม่ดีนัก

PS : พารามิเตอร์เริ่มต้นจะช่วยแก้ปัญหานี้สำหรับ C # 4.0 หรือจะเรียกว่าตัวสร้างเริ่มต้นที่กำหนดโดย CLR


Jon Skeetตอบ:

หากต้องการใช้ตัวอย่างของคุณคุณต้องการทำอะไรเมื่อมีคนทำ:

 Rational[] fractions = new Rational[1000];

มันควรวิ่งผ่านตัวสร้างของคุณ 1,000 ครั้งหรือไม่

แน่นอนมันเป็นเหตุผลที่ฉันเขียนตัวสร้างเริ่มต้นตั้งแต่แรก CLR ควรใช้ตัวสร้างzeroing เริ่มต้นเมื่อไม่มีการกำหนดตัวสร้างเริ่มต้นที่ชัดเจน; ด้วยวิธีนี้คุณจ่ายเฉพาะสิ่งที่คุณใช้ ถ้าฉันต้องการตู้คอนเทนเนอร์ 1,000 กล่องไม่ใช่ค่าเริ่มต้นRational (และต้องการปรับการสร้าง 1,000 ครั้งให้เหมาะสม) ฉันจะใช้List<Rational>อาร์เรย์แทน

ในใจของฉันเหตุผลนี้ไม่แข็งแรงพอที่จะป้องกันคำจำกัดความของตัวสร้างเริ่มต้น


3
+1 มีปัญหาที่คล้ายกันหนึ่งครั้งในที่สุดก็เปลี่ยนโครงสร้างเป็นคลาส
Dirk Vollmar

4
พารามิเตอร์เริ่มต้นใน C # 4 ไม่สามารถช่วยเพราะRational()เรียก ctor parameterless Rational(long num=0, long denom=1)มากกว่า
LaTeX

6
โปรดทราบว่าในC # 6.0ซึ่งมาพร้อมกับ Visual Studio 2015 จะอนุญาตให้เขียนตัวสร้างอินสแตนซ์พารามิเตอร์ศูนย์สำหรับ structs ดังนั้นnew Rational()จะเรียก constructor ถ้ามันมีอยู่ แต่ถ้ามันไม่ได้อยู่ที่จะเทียบเท่ากับnew Rational() default(Rational)ในกรณีใด ๆ คุณควรใช้ไวยากรณ์default(Rational)เมื่อคุณต้องการ "ศูนย์ค่า" ของ struct ของคุณ (ซึ่งเป็นหมายเลข "ไม่ดี" กับการออกแบบที่เสนอของคุณRational) ค่าเริ่มต้นสำหรับประเภทค่าอยู่เสมอT default(T)ดังนั้นnew Rational[1000]จะไม่เรียกใช้ constructors construct
Jeppe Stig Nielsen

6
เพื่อแก้ปัญหานี้โดยเฉพาะคุณสามารถเก็บdenominator - 1ไว้ใน struct เพื่อให้ค่าเริ่มต้นกลายเป็น 0/1
miniBill

3
Then if I want a container of 1000 non-default Rationals (and want to optimize away the 1000 constructions) I will use a List<Rational> rather than an array.เหตุใดคุณจึงคาดหวังให้อาร์เรย์เรียกใช้ตัวสร้างที่แตกต่างกันไปยังรายการสำหรับโครงสร้าง
mjwills

คำตอบ:


197

หมายเหตุ:คำตอบดังต่อไปนี้ถูกเขียนขึ้นเป็นเวลานานก่อนที่จะ C # 6 ซึ่งมีการวางแผนที่จะแนะนำความสามารถในการประกาศก่อสร้าง parameterless ใน structs - แต่พวกเขาจะยังคงไม่ถูกเรียกในทุกสถานการณ์ (เช่นสำหรับการสร้างอาร์เรย์) (ในท้ายที่สุด คุณสมบัตินี้ไม่ได้ถูกเพิ่มใน C # 6 )


แก้ไข: ฉันได้แก้ไขคำตอบด้านล่างเนื่องจากความเข้าใจของ Grauenwolf ใน CLR

CLR อนุญาตให้ประเภทค่ามีตัวสร้างแบบไร้พารามิเตอร์ แต่ C # ไม่มี ฉันเชื่อว่านี่เป็นเพราะมันจะแนะนำความคาดหวังว่านวกรรมิกจะถูกเรียกเมื่อมันไม่เกิดขึ้น ตัวอย่างเช่นพิจารณาสิ่งนี้:

MyStruct[] foo = new MyStruct[1000];

CLR สามารถทำสิ่งนี้ได้อย่างมีประสิทธิภาพเพียงแค่จัดสรรหน่วยความจำที่เหมาะสมและปล่อยให้เป็นศูนย์ หากต้องเรียกใช้ตัวสร้าง MyStruct 1,000 ครั้งนั่นจะมีประสิทธิภาพน้อยกว่ามาก (ในความเป็นจริงมันไม่ได้ - ถ้าคุณทำมี constructor parameterless ก็ไม่ได้รับการเรียกใช้เมื่อคุณสร้างอาร์เรย์หรือเมื่อคุณมีตัวแปรเช่นเตรียม.)

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

แก้ไข: ในการใช้ตัวอย่างของคุณสิ่งที่คุณต้องการจะเกิดขึ้นเมื่อมีคนทำ:

Rational[] fractions = new Rational[1000];

มันควรวิ่งผ่านตัวสร้างของคุณ 1,000 ครั้งหรือไม่

  • ถ้าไม่เราจะจบด้วย 1,000 เหตุผลที่ไม่ถูกต้อง
  • ถ้าเป็นเช่นนั้นเราอาจสูญเสียภาระงานหากเรากำลังจะกรอกข้อมูลในอาร์เรย์ด้วยค่าจริง

แก้ไข: (ตอบคำถามอีกเล็กน้อย) ตัวสร้างแบบไม่มีพารามิเตอร์ไม่ได้ถูกสร้างโดยคอมไพเลอร์ ประเภทค่าไม่ได้ที่จะมีการก่อสร้างเท่าที่ CLR เป็นห่วง - แม้ว่ามันจะเปิดออกก็สามารถถ้าคุณเขียนไว้ในอิลลินอยส์ เมื่อคุณเขียน " new Guid()" ใน C # ที่ปล่อย IL แตกต่างจากที่คุณได้รับถ้าคุณเรียกตัวสร้างแบบปกติ ดูคำถาม SO นี้อีกเล็กน้อยในมุมมองนั้น

ฉันสงสัยว่าไม่มีประเภทของค่าใด ๆ ในกรอบงานที่มีตัวสร้างแบบไม่มีพารามิเตอร์ ไม่ต้องสงสัยเลย NDepend สามารถบอกฉันได้ว่าฉันถามอย่างดีพอหรือไม่ ... ความจริงที่ว่า C # ห้ามมันเป็นคำใบ้ที่ใหญ่พอสำหรับฉันที่จะคิดว่ามันอาจเป็นความคิดที่ไม่ดี


9
คำอธิบายที่สั้นกว่า: ใน C ++ โครงสร้างและคลาสเป็นเพียงสองด้านของเหรียญเดียวกัน ข้อแตกต่างที่แท้จริงเพียงอย่างเดียวคือข้อใดข้อหนึ่งเป็นแบบสาธารณะโดยค่าเริ่มต้นและอีกข้อหนึ่งเป็นแบบส่วนบุคคล ใน. Net มีความแตกต่างมากขึ้นระหว่าง struct และคลาสและสิ่งสำคัญคือต้องเข้าใจ
Joel Coehoorn

38
@Joel: นั่นไม่ได้อธิบายถึงข้อ จำกัด นี้จริงๆใช่ไหม?
Jon Skeet

6
CLR อนุญาตให้ประเภทค่ามีตัวสร้างแบบไร้พารามิเตอร์ และใช่มันจะรันมันสำหรับแต่ละองค์ประกอบในอาร์เรย์ C # คิดว่านี่เป็นความคิดที่ไม่ดีและไม่อนุญาต แต่คุณสามารถเขียนภาษา. NET ได้
Jonathan Allen

2
ขออภัยฉันสับสนกับสิ่งต่อไปนี้ ไม่ต้องRational[] fractions = new Rational[1000];เสียภาระงานถ้าRationalเป็นคลาสแทนที่จะเป็น struct หรือไม่? ถ้าเป็นเช่นนั้นทำไมคลาสถึงมี ctor เริ่มต้น
จูบรักแร้ของฉัน

5
@ FifaEarthCup2014: คุณต้องเจาะจงมากขึ้นเกี่ยวกับสิ่งที่คุณหมายถึงโดย "เสียภาระงาน" แต่เมื่อทางใดทางหนึ่งมันจะไม่เรียกตัวสร้าง 1,000 ครั้ง ถ้าRationalเป็นคลาสคุณจะจบด้วยอาร์เรย์ของการอ้างอิง null 1,000 รายการ
Jon Skeet

48

struct คือชนิดของค่าและชนิดของค่าต้องมีค่าเริ่มต้นทันทีที่มีการประกาศ

MyClass m;
MyStruct m2;

หากคุณประกาศสองฟิลด์ดังกล่าวข้างต้นโดยไม่ต้องสร้างอินสแตนซ์ให้กับตัวแบ่งการดีบักเกอร์mจะเป็นโมฆะ แต่m2จะไม่ ด้วยสิ่งนี้คอนสตรัคเตอร์แบบไม่มีพารามิเตอร์จะไม่สมเหตุสมผลในความเป็นจริงคอนสตรัคเตอร์ใด ๆ บนโครงสร้างจะกำหนดค่าสิ่งที่มีอยู่แล้วเพียงแค่ประกาศ อันที่จริง m2 สามารถใช้อย่างมีความสุขในตัวอย่างข้างต้นและมีวิธีการที่เรียกว่าถ้ามีและเขตข้อมูลและคุณสมบัติของมันจัดการ!


3
ไม่แน่ใจว่าทำไมมีคนโหวตให้คุณ คุณดูเหมือนจะเป็นคำตอบที่ถูกต้องที่สุดที่นี่
pipTheGeek

12
พฤติกรรมใน C ++ คือถ้าประเภทมีคอนสตรัคเตอร์เริ่มต้นจากนั้นจะถูกใช้เมื่อสร้างวัตถุดังกล่าวโดยไม่มีคอนสตรัคเตอร์ที่ชัดเจน สิ่งนี้อาจถูกใช้ใน C # เพื่อเริ่มต้น m2 ด้วยตัวสร้างเริ่มต้นซึ่งเป็นสาเหตุที่คำตอบนี้ไม่มีประโยชน์
Motti

3
onester: หากคุณไม่ต้องการให้ structs เรียกคอนสตรัคเตอร์ของตัวเองเมื่อประกาศแล้วอย่ากำหนดคอนสตรัคเตอร์เริ่มต้นเช่นนี้! :) นั่นคือคำพูดของ Motti
Stefan Monov

8
@Tarik ผมไม่เห็นด้วย. ในทางตรงกันข้ามคอนสตรัคเตอร์แบบไม่มีพารามิเตอร์จะทำให้เข้าใจได้อย่างสมบูรณ์: ถ้าฉันต้องการสร้าง "เมทริกซ์" struct ซึ่งมักจะมีเมทริกซ์เอกลักษณ์เป็นค่าเริ่มต้นคุณจะทำมันด้วยวิธีอื่นได้อย่างไร?
Elo

1
ผมไม่แน่ใจว่าผมอย่างเห็นด้วยกับ"แท้จริง m2 จะค่อนข้างมีความสุขที่จะใช้ .." อาจเป็นจริงใน C # ก่อนหน้า แต่เป็นข้อผิดพลาดของคอมไพเลอร์ที่จะประกาศ struct ไม่ใช่newมันแล้วลองใช้สมาชิก
Caius Jard

18

แม้ว่า CLR อนุญาตให้ทำได้ C # ไม่อนุญาตให้ structs มีตัวสร้างพารามิเตอร์น้อยกว่าค่าเริ่มต้น เหตุผลก็คือสำหรับประเภทค่าคอมไพเลอร์ตามค่าเริ่มต้นจะไม่สร้างคอนสตรัคเตอร์เริ่มต้นหรือพวกเขาไม่สร้างการเรียกไปยังตัวสร้างเริ่มต้น ดังนั้นแม้ว่าคุณจะกำหนดคอนสตรัคเตอร์เริ่มต้นมันจะไม่ถูกเรียกและนั่นจะทำให้คุณสับสนเท่านั้น

เพื่อหลีกเลี่ยงปัญหาดังกล่าวคอมไพเลอร์ C # ไม่อนุญาตให้ผู้ใช้กำหนดคอนสตรัคเตอร์เริ่มต้น และเนื่องจากมันไม่ได้สร้างนวกรรมิกเริ่มต้นคุณไม่สามารถเริ่มต้นเขตข้อมูลเมื่อกำหนดได้

หรือเหตุผลใหญ่คือโครงสร้างเป็นชนิดค่าและชนิดของค่าเริ่มต้นโดยค่าเริ่มต้นและใช้ตัวสร้างสำหรับการเริ่มต้น

คุณไม่จำเป็นต้องสร้างอินสแตนซ์ของคุณด้วยnewคำสำคัญ มันทำงานเหมือน int; คุณสามารถเข้าถึงได้โดยตรง

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

คอนสตรัคเตอร์ (พารามิเตอร์น้อยกว่า) เริ่มต้นสำหรับ struct สามารถตั้งค่าที่แตกต่างจากสถานะทั้งหมดเป็นศูนย์ซึ่งจะเป็นพฤติกรรมที่ไม่คาดคิด . NET runtime จึงห้ามไม่ให้ผู้สร้างเริ่มต้นสำหรับ struct


คำตอบนี้ดีที่สุด ในท้ายที่สุดจุดทั้งหมดของการ จำกัด คือการหลีกเลี่ยงความประหลาดใจเช่นMyStruct s;ไม่เรียกตัวสร้างเริ่มต้นที่คุณให้
สูง

1
ขอบคุณสำหรับคำอธิบาย ดังนั้นจึงเป็นเพียงการขาดคอมไพเลอร์ที่จะต้องมีการปรับปรุงไม่มีเหตุผลที่ดีในทางทฤษฎีที่จะห้ามการสร้างแบบไม่มีพารามิเตอร์ (ทันทีที่พวกเขาสามารถ จำกัด การเข้าถึงคุณสมบัติเท่านั้น)
Elo

16

คุณสามารถสร้างคุณสมบัติสแตติกที่เริ่มต้นและส่งคืนหมายเลข "rational" ที่เป็นค่าเริ่มต้น:

public static Rational One => new Rational(0, 1); 

และใช้มันเช่น:

var rat = Rational.One;

24
ในกรณีนี้Rational.Zeroอาจทำให้สับสนน้อยลงเล็กน้อย
เควิน

13

คำอธิบายที่สั้นกว่า:

ใน C ++ โครงสร้างและคลาสเป็นเพียงสองด้านของเหรียญเดียวกัน ข้อแตกต่างที่แท้จริงเพียงอย่างเดียวคือข้อใดข้อหนึ่งเป็นแบบสาธารณะโดยค่าเริ่มต้นและอีกข้อหนึ่งเป็นแบบส่วนตัว

ใน. NETมีความแตกต่างมากขึ้นระหว่าง struct และคลาส สิ่งสำคัญคือ struct ให้ความหมายประเภทค่าในขณะที่คลาสให้ความหมายประเภทอ้างอิง เมื่อคุณเริ่มคิดเกี่ยวกับความหมายของการเปลี่ยนแปลงนี้การเปลี่ยนแปลงอื่น ๆ ก็เริ่มมีเหตุผลมากขึ้นเช่นกันรวมถึงพฤติกรรมของตัวสร้างที่คุณอธิบาย


7
คุณจะต้องเป็นบิตที่ชัดเจนมากขึ้นเกี่ยวกับวิธีการนี้ก็ส่อให้เห็นโดยแบ่งตามชนิดอ้างอิงคุ้มค่าเมื่อเทียบกับแบ่งฉันไม่ได้รับมัน ...
Motti

ประเภทค่ามีค่าเริ่มต้น - พวกเขาไม่ใช่โมฆะแม้ว่าคุณจะไม่ได้กำหนดนวกรรมิก ในขณะที่ภาพรวมในครั้งแรกสิ่งนี้ไม่ได้ จำกัด เฉพาะการกำหนดตัวสร้างเริ่มต้นกรอบการใช้คุณลักษณะนี้ภายในเพื่อสร้างข้อสันนิษฐานบางอย่างเกี่ยวกับ structs
Joel Coehoorn

@annakata: คอนสตรัคเตอร์อื่นอาจมีประโยชน์ในบางสถานการณ์ที่เกี่ยวข้องกับ Reflection นอกจากนี้หากมีการปรับปรุงข้อมูลทั่วไปเพื่ออนุญาตให้มีข้อ จำกัด "ใหม่" ของพารามิเตอร์มันจะมีประโยชน์หากมีโครงสร้างที่สามารถปฏิบัติตามได้
supercat

@annakata ฉันเชื่อว่าเป็นเพราะ C # มีความต้องการที่แข็งแกร่งเป็นพิเศษที่newต้องเขียนเพื่อเรียกคอนสตรัคเตอร์ ใน C ++ constructors ถูกเรียกในลักษณะที่ซ่อนเร้นเมื่อมีการประกาศหรือ instanciation ของ arrays ใน C # ไม่ว่าจะเป็นตัวชี้ทุกอย่างเริ่มต้นที่ null ไม่ว่าจะเป็น struct และต้องเริ่มที่บางอย่าง แต่เมื่อคุณไม่สามารถเขียนnew...
v.oddou

3

ฉันไม่เห็นวิธีแก้ปัญหาที่ช้ากว่าที่ฉันจะให้ดังนั้นนี่คือ

ใช้ offsets เพื่อย้ายค่าจากค่าเริ่มต้น 0 ไปเป็นค่าใด ๆ ที่คุณต้องการ ที่นี่ต้องใช้คุณสมบัติแทนการเข้าถึงฟิลด์โดยตรง (อาจมีคุณสมบัติ c # 7 ที่เป็นไปได้คุณควรกำหนดเขตข้อมูลคุณสมบัติขอบเขตเพื่อให้เขตข้อมูลเหล่านั้นได้รับการปกป้องจากการเข้าถึงโดยตรงในโค้ด)

โซลูชันนี้ใช้ได้กับโครงสร้างอย่างง่ายที่มีเฉพาะประเภทค่า (ไม่มีประเภทการอ้างอิงหรือโครงสร้างที่ไม่มีค่า)

public struct Tempo
{
    const double DefaultBpm = 120;
    private double _bpm; // this field must not be modified other than with its property.

    public double BeatsPerMinute
    {
        get => _bpm + DefaultBpm;
        set => _bpm = value - DefaultBpm;
    }
}

นี่แตกต่างจากคำตอบนี้วิธีนี้ไม่ได้เป็นแบบเฉพาะ แต่เป็นการใช้ offset ซึ่งจะใช้ได้กับทุกช่วง

ตัวอย่างที่มี enums เป็นสนาม

public struct Difficaulty
{
    Easy,
    Medium,
    Hard
}

public struct Level
{
    const Difficaulty DefaultLevel = Difficaulty.Medium;
    private Difficaulty _level; // this field must not be modified other than with its property.

    public Difficaulty Difficaulty
    {
        get => _level + DefaultLevel;
        set => _level = value - DefaultLevel;
    }
}

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


นี่เป็นทางออกที่ดีสำหรับตัวอย่างที่ฉันให้ แต่จริง ๆ แล้วมันควรจะเป็นตัวอย่างเท่านั้นคำถามทั่วไป
Motti

2

เพียงแค่เป็นกรณีพิเศษ หากคุณเห็นตัวเศษเป็น 0 และตัวส่วนเป็น 0 ให้ทำเป็นว่ามันมีค่าที่คุณต้องการจริงๆ


5
โดยส่วนตัวแล้วฉันไม่ต้องการให้ชั้นเรียน / โครงสร้างของฉันมีพฤติกรรมแบบนี้ ความล้มเหลวอย่างเงียบ ๆ (หรือฟื้นตัวในลักษณะที่ผู้พัฒนาคิดว่าดีที่สุดสำหรับคุณ) คือหนทางสู่ความผิดพลาด
Boris Callens

2
+1 นี่คือคำตอบที่ดีเพราะสำหรับประเภทค่าคุณต้องคำนึงถึงค่าเริ่มต้น สิ่งนี้ให้คุณ "ตั้งค่า" เป็นค่าเริ่มต้นพร้อมกับพฤติกรรม
IllidanS4 ต้องการโมนิก้ากลับเมื่อ

นี่เป็นวิธีที่พวกเขาใช้คลาสเช่นNullable<T>(เช่นint?)
Jonathan Allen

นั่นเป็นความคิดที่แย่มาก 0/0 ควรเป็นเศษส่วนที่ไม่ถูกต้อง (NaN) เสมอ เกิดอะไรขึ้นถ้าใครบางคนเรียกnew Rational(x,y)ว่า x และ y เป็น 0
Mike Rosoft

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

2

สิ่งที่ฉันใช้คือ ตัวดำเนินการรวมศูนย์ (??)รวมกับเขตข้อมูลสำรองเช่นนี้:

public struct SomeStruct {
  private SomeRefType m_MyRefVariableBackingField;

  public SomeRefType MyRefVariable {
    get { return m_MyRefVariableBackingField ?? (m_MyRefVariableBackingField = new SomeRefType()); }
  }
}

หวังว่านี่จะช่วยได้;)

หมายเหตุ: การกำหนดค่ารวมศูนย์เป็นปัจจุบันเป็นข้อเสนอคุณสมบัติสำหรับ C # 8.0


1

คุณไม่สามารถกำหนดนวกรรมิกเริ่มต้นได้เนื่องจากคุณใช้ C #

โครงสร้างสามารถมีตัวสร้างเริ่มต้นใน. NET แต่ฉันไม่รู้ภาษาเฉพาะใด ๆ ที่สนับสนุน


ใน C # คลาสและ structs แตกต่างกันในเชิงความหมาย struct คือชนิดของค่าในขณะที่คลาสเป็นชนิดของการอ้างอิง
Tom Sarduy

-1

นี่คือวิธีแก้ปัญหาของฉันสำหรับภาวะที่ไม่มีค่าเริ่มต้น ฉันรู้ว่านี่เป็นวิธีแก้ปัญหาที่ล่าช้า แต่ฉันคิดว่ามันคุ้มค่าที่จะทราบว่านี่เป็นวิธีแก้ปัญหา

public struct Point2D {
    public static Point2D NULL = new Point2D(-1,-1);
    private int[] Data;

    public int X {
        get {
            return this.Data[ 0 ];
        }
        set {
            try {
                this.Data[ 0 ] = value;
            } catch( Exception ) {
                this.Data = new int[ 2 ];
            } finally {
                this.Data[ 0 ] = value;
            }
        }
    }

    public int Z {
        get {
            return this.Data[ 1 ];
        }
        set {
            try {
                this.Data[ 1 ] = value;
            } catch( Exception ) {
                this.Data = new int[ 2 ];
            } finally {
                this.Data[ 1 ] = value;
            }
        }
    }

    public Point2D( int x , int z ) {
        this.Data = new int[ 2 ] { x , z };
    }

    public static Point2D operator +( Point2D A , Point2D B ) {
        return new Point2D( A.X + B.X , A.Z + B.Z );
    }

    public static Point2D operator -( Point2D A , Point2D B ) {
        return new Point2D( A.X - B.X , A.Z - B.Z );
    }

    public static Point2D operator *( Point2D A , int B ) {
        return new Point2D( B * A.X , B * A.Z );
    }

    public static Point2D operator *( int A , Point2D B ) {
        return new Point2D( A * B.Z , A * B.Z );
    }

    public override string ToString() {
        return string.Format( "({0},{1})" , this.X , this.Z );
    }
}

เพิกเฉยต่อความจริงที่ว่าฉันมีโครงสร้างแบบคงที่ที่เรียกว่าเป็นโมฆะ (หมายเหตุ: นี่สำหรับจตุภาคบวกทั้งหมดเท่านั้น) โดยใช้ get; set; ใน C # คุณสามารถลอง / catch / สุดท้ายสำหรับการจัดการกับข้อผิดพลาดที่ชนิดข้อมูลเฉพาะไม่เริ่มต้นโดยตัวสร้างเริ่มต้น Point2D () ฉันเดาว่านี่เป็นคำตอบที่เข้าใจยากสำหรับบางคนในคำตอบนี้ นั่นเป็นสาเหตุที่ฉันเพิ่มของฉัน การใช้ฟังก์ชั่น getter และ setter ใน C # จะช่วยให้คุณหลีกเลี่ยง Constructor ที่เป็นค่าเริ่มต้นและลองทำตามสิ่งที่คุณไม่ได้กำหนดค่าเริ่มต้นไว้ สำหรับฉันมันใช้งานได้ดีสำหรับคนอื่นที่คุณอาจต้องการเพิ่มบางคำสั่งถ้า ดังนั้นในกรณีที่คุณต้องการตั้งค่าตัวเศษ / ส่วนรหัสนี้อาจช่วยได้ ฉันแค่อยากจะย้ำว่าวิธีนี้ไม่ได้ดูดีอาจจะทำงานได้แย่ลงจากมุมมองที่มีประสิทธิภาพ แต่ สำหรับบางคนที่มาจาก C # รุ่นเก่าการใช้ประเภทข้อมูลอาเรย์ช่วยให้คุณใช้ฟังก์ชันนี้ได้ หากคุณต้องการบางสิ่งที่ใช้งานได้ลองสิ่งนี้:

public struct Rational {
    private long[] Data;

    public long Numerator {
        get {
            try {
                return this.Data[ 0 ];
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                return this.Data[ 0 ];
            }
        }
        set {
            try {
                this.Data[ 0 ] = value;
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                this.Data[ 0 ] = value;
            }
        }
    }

    public long Denominator {
        get {
            try {
                return this.Data[ 1 ];
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                return this.Data[ 1 ];
            }
        }
        set {
            try {
                this.Data[ 1 ] = value;
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                this.Data[ 1 ] = value;
            }
        }
    }

    public Rational( long num , long denom ) {
        this.Data = new long[ 2 ] { num , denom };
        /* Todo: Find GCD etc. */
    }

    public Rational( long num ) {
        this.Data = new long[ 2 ] { num , 1 };
        this.Numerator = num;
        this.Denominator = 1;
    }
}

2
นี่เป็นรหัสที่แย่มาก ทำไมคุณถึงมีการอ้างอิงอาร์เรย์ใน struct? ทำไมคุณไม่มีพิกัด X และ Y เป็นเขตข้อมูล และการใช้ข้อยกเว้นสำหรับการควบคุมการไหลเป็นความคิดที่ไม่ดี โดยทั่วไปคุณควรเขียนโค้ดของคุณในลักษณะที่ NullReferenceException ไม่เคยเกิดขึ้น ถ้าคุณต้องการสิ่งนี้จริงๆ - แม้ว่าสิ่งก่อสร้างเช่นนั้นจะเหมาะกว่าสำหรับคลาสแทนที่จะเป็นแบบโครงสร้าง - จากนั้นคุณควรใช้การเริ่มต้นแบบขี้เกียจ (และในทางเทคนิคคุณเป็น - ไม่จำเป็นอย่างสมบูรณ์ในทุก ๆ แต่การตั้งค่าแรกของพิกัด - การตั้งค่าแต่ละพิกัดสองครั้ง)
Mike Rosoft

-1
public struct Rational 
{
    private long numerator;
    private long denominator;

    public Rational(long num = 0, long denom = 1)   // This is allowed!!!
    {
        numerator   = num;
        denominator = denom;
    }
}

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