ตัวอย่างของการบรรทุกเกินพิกัดซึ่งสมเหตุสมผล [ปิด]


12

ในขณะที่ฉันเรียนรู้ C # ฉันพบว่า C # รองรับการบรรทุกเกินพิกัด ฉันมีปัญหากับตัวอย่างที่ดีซึ่ง:

  1. Make sense (เช่นเพิ่มคลาสที่ชื่อแกะและวัว)
  2. ไม่ใช่ตัวอย่างของการต่อข้อมูลสองสตริง

ตัวอย่างจาก Base Class Library ยินดีต้อนรับ


10
กำหนด 'ความรู้สึก' ได้โปรด! การถกเถียงกันอย่างขมขื่นและเร่าร้อนอย่างจริงจังในจุดนี้แสดงให้เห็นว่ามีความขัดแย้งอย่างมากเกี่ยวกับเรื่องนี้ เจ้าหน้าที่จำนวนมากปฏิเสธผู้ปฏิบัติงานที่ทำงานหนักเกินไปเพราะสามารถทำสิ่งที่ไม่คาดคิดได้อย่างเต็มที่ คนอื่น ๆ ตอบว่าชื่อวิธีการนั้นสามารถถูกเลือกให้ใช้งานโดยไม่ได้ตั้งใจ แต่นั่นก็เป็นเหตุผลว่าทำไมการปฏิเสธบล็อคโค้ดที่มีชื่อ! คุณเกือบจะไม่ได้รับตัวอย่างที่ถือว่าสมเหตุสมผล ตัวอย่างที่เหมาะสมกับคุณ - อาจเป็น
Kilian Foth

เห็นด้วยอย่างสมบูรณ์กับ @KilianFoth ในที่สุดโปรแกรมที่คอมไพล์จะทำให้รู้สึกถึงคอมไพเลอร์ แต่ถ้าการโอเวอร์โหลด==ทำทวีคูณมันสมเหตุสมผลสำหรับฉัน แต่อาจไม่สมเหตุสมผลสำหรับผู้อื่น! คำถามนี้เกี่ยวกับความถูกต้องของภาษาโปรแกรมสิ่งอำนวยความสะดวกหรือเรากำลังพูดถึง 'การเขียนโค้ดวิธีปฏิบัติที่ดีที่สุด' หรือไม่?
Dipan Mehta

คำตอบ:


27

ตัวอย่างที่ชัดเจนของการบรรทุกเกินพิกัดที่เหมาะสมคือคลาสใด ๆ ที่ทำงานในลักษณะเดียวกับที่ใช้งานตัวเลข ดังนั้นbigintเรียน (ตามJalaynแสดงให้เห็น) ตัวเลขที่ซับซ้อนหรือเมทริกซ์ชั้นเรียน (ตามSuperbestแสดงให้เห็น) ทุกคนมีการดำเนินงานเดียวกันว่าตัวเลขสามัญได้ map เพื่อให้ได้ดีจริงๆบนดำเนินการทางคณิตศาสตร์ในขณะที่เวลาการดำเนินงาน (แนะนำโดยsvick ) แผนที่อย่างบนย่อย ของการดำเนินงานเหล่านั้น

เล็กน้อย abstractly ผู้ประกอบการสามารถนำมาใช้เมื่อดำเนินการชุดเช่นการดำเนินงานดังนั้นoperator+อาจจะเป็นสหภาพ , operator-อาจจะเป็นส่วนเติมเต็มฯลฯ นี้ไม่เริ่มต้นที่จะยืดกระบวนทัศน์แม้ว่าโดยเฉพาะอย่างยิ่งถ้าคุณใช้นอกจากนี้ผู้ประกอบการหรือคูณสำหรับการดำเนินงานซึ่ง ISN' เกิด ไม่ต้องสับเปลี่ยนอย่างที่คุณคาดหวัง

C # มีตัวอย่างที่ยอดเยี่ยมของการใช้งานตัวดำเนินการมากเกินไปที่ไม่ใช่ตัวเลข มันใช้+=และ-=เพื่อเพิ่มและลบผู้ได้รับมอบหมายเช่นลงทะเบียนและยกเลิกการลงทะเบียนพวกเขา วิธีนี้ใช้งานได้ดีเนื่องจากตัวดำเนินการ+=และ-=ตัวดำเนินการทำงานตามที่คุณคาดไว้และทำให้โค้ดที่กระชับยิ่งขึ้น

สำหรับคนพิถีพิถันปัญหาอย่างหนึ่งของตัว+ดำเนินการสตริงคือมันไม่ได้สลับกัน ไม่ได้เป็นเช่นเดียวกับ"a"+"b" "b"+"a"เราเข้าใจข้อยกเว้นนี้สำหรับสตริงเพราะมันเป็นเรื่องธรรมดา แต่เราจะบอกได้อย่างไรว่าการใช้operator+กับประเภทอื่นจะเป็นแบบสลับกันหรือไม่ คนส่วนใหญ่จะคิดว่ามันเป็นเว้นแต่ว่าวัตถุนั้นเป็นเหมือนสตริงแต่คุณไม่เคยรู้เลยว่าคนอื่นจะสมมติอะไร

เช่นเดียวกับสตริงค่าของเมทริกซ์ที่เป็นที่รู้จักก็ค่อนข้างดีเช่นกัน เห็นได้ชัดว่าMatrix operator* (double, Matrix)เป็นการคูณสเกลาร์ในขณะที่Matrix operator* (Matrix, Matrix)จะเป็นการคูณเมทริกซ์ (เช่นเมทริกซ์ของการคูณดอทผลิตภัณฑ์)

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

อนึ่งที่ประชุม ACCU 2011 , โรเจอร์ออร์และสตีฟความรักที่นำเสนอเซสชั่นเกี่ยวกับวัตถุบางอย่างมีค่าเท่ากันมากกว่าคนอื่น ๆ - ดูที่หลายความหมายของความเสมอภาคที่คุ้มค่าและตัวตน ของพวกเขาภาพนิ่งสามารถดาวน์โหลดได้เช่นเดียวกับริชาร์ดแฮร์ริสภาคผนวกเกี่ยวกับความเสมอภาคลอยจุด สรุป: ระวังตัวด้วยoperator==นี่ต้องเป็นมังกร!

การบรรทุกเกินพิกัดเป็นเทคนิคความหมายที่ทรงพลังมาก แต่ง่ายต่อการใช้งานมากเกินไป เป็นการดีที่คุณควรใช้ในสถานการณ์ที่ชัดเจนมากจากบริบทว่าผลกระทบของโอเปอเรเตอร์ที่โอเวอร์โหลดคืออะไร ในหลาย ๆ วิธีa.union(b)เป็นที่ชัดเจนกว่าa+bและa*bเป็นมากมากขึ้นชัดเจนกว่าa.cartesianProduct(b)โดยเฉพาะอย่างยิ่งนับตั้งแต่ผลของผลิตภัณฑ์คาร์ทีเซียนจะเป็นมากกว่าSetLike<Tuple<T,T>>SetLike<T>

ปัญหาที่แท้จริงของตัวดำเนินการโอเวอร์โหลดเกิดขึ้นเมื่อโปรแกรมเมอร์สันนิษฐานว่าคลาสจะทำงานในทิศทางเดียว แต่จริง ๆ แล้วมันจะทำงานในอีกทางหนึ่ง การปะทะแบบ semantic แบบนี้คือสิ่งที่ฉันแนะนำให้พยายามหลีกเลี่ยง


1
คุณบอกว่าตัวดำเนินการบนแมทริกซ์แมปดีมาก แต่การคูณเมทริกซ์ไม่ได้สลับกันเช่นกัน ผู้ประกอบการที่ได้รับมอบหมายนั้นแข็งแกร่งยิ่งขึ้น คุณสามารถทำได้d1 + d2สำหรับตัวแทนสองคนที่เป็นประเภทเดียวกัน
svick

1
@ Mark: "ผลิตภัณฑ์ดอท" ถูกกำหนดไว้เฉพาะบนเวคเตอร์ การคูณเมทริกซ์สองตัวเรียกว่า "การคูณเมทริกซ์" ความแตกต่างที่เป็นมากกว่าความหมายเพียงแค่: ผลตอบแทน dot ผลิตภัณฑ์เกลาขณะเมทริกซ์ผลตอบแทนคูณเมทริกซ์(และโดยวิธีการที่ไม่ใช่การสับเปลี่ยน)
BlueRaja - Danny Pflughoeft

26

ฉันไม่มีใครแปลกใจที่กล่าวถึงหนึ่งในกรณีที่น่าสนใจมากขึ้นใน BCL: และDateTime TimeSpanคุณสามารถ:

  • เพิ่มหรือลบสองTimeSpans เพื่อรับอีกTimeSpan
  • ใช้ลบเอกในTimeSpanที่จะได้รับเมื่อตะกี้TimeSpan
  • ลบสองDateTimes เพื่อให้ได้ aTimeSpan
  • เพิ่มหรือลบTimeSpanจาก a DateTimeเพื่อรับค่าอื่นDateTime

ชุดของผู้ประกอบการที่จะทำให้ความรู้สึกในหลายประเภทเป็นอีก<, >, ,<= >=ใน BCL ตัวอย่างเช่นVersionการดำเนินการกับพวกเขา


ตัวอย่างที่แท้จริงมาก ๆ มากกว่าทฤษฎีอวดความรู้!
SIslam

7

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

นอกจากนี้เนื่องจากฉันยังทำ Java และ Java ไม่อนุญาตให้ตัวดำเนินการโอเวอร์โหลดดังนั้นจึงมีความหวานในการเขียนอย่างไม่น่าเชื่อ

BigInteger bi = new BigInteger(0);
bi += 10;

กว่าใน Java:

BigDecimal bd = new BigDecimal(0);
bd = bd.add(new BigDecimal(10));

5

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

ดังนั้น Irony คือ ". NET Language Implementation Kit" และเป็นเครื่องมือสร้างวิเคราะห์คำ (สร้าง LALR parser) แทนที่จะต้องเรียนรู้ไวยากรณ์ / ภาษาใหม่เช่นตัวแยกวิเคราะห์เช่น yacc / lex คุณเขียนไวยากรณ์ใน C # ด้วยตัวดำเนินการโอเวอร์โหลด นี่คือไวยากรณ์ BNFอย่างง่าย

// BNF 
Expr := Term | BinExpr
Term := number | ParExpr
ParExpr := "(" + Expr + ")"
BinExpr := number + BinOp + number
BinOp := "+" | "-" | "*" | "/"
number := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

ดังนั้นมันจึงเป็นไวยากรณ์เล็ก ๆ ง่ายๆ (โปรดแก้ตัวถ้ามีความไม่สอดคล้องกันเพราะฉันเพิ่งเรียน BNF และสร้างไวยากรณ์) ตอนนี้ให้ดูที่ C #:

  var Expr = new NonTerminal("Expr");
  var Term = new NonTerminal("Term");
  var BinExpr = new NonTerminal("BinExpr");
  var ParExpr = new NonTerminal("ParExpr");
  var BinOp = new NonTerminal("BinOp");
  var Statement = new NonTerminal("Statement");
  var ProgramLine = new NonTerminal("ProgramLine");
  var Program = new NonTerminal("Program", typeof(StatementListNode));
  // BNF Rules - Overloading
  Expr.Rule = Term | BinExpr;
  Term.Rule = number | ParExpr;
  ParExpr.Rule = "(" + Expr + ")";
  BinExpr.Rule = Expr + BinOp + Expr;
  BinOp.Rule = ToTerm("+") | "-" | "*" | "/" | "**";

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


3

ตัวอย่างที่สำคัญคือโอเปอร์เรเตอร์ == / โอเปอเรเตอร์! =

หากคุณต้องการเปรียบเทียบสองวัตถุด้วยค่าข้อมูลแทนการอ้างอิงอย่างง่ายดายคุณจะต้องโอเวอร์โหลด .Equals (และ.GetHashCode!) และอาจต้องการใช้ตัวดำเนินการ! = และ == เพื่อความสอดคล้อง

ฉันไม่เคยเห็นโอเวอร์โหลดขนาดใหญ่ของผู้ให้บริการรายอื่นในภาษา C # (ฉันคิดว่ามีกรณีเล็ก ๆ ที่อาจเป็นประโยชน์ได้)


1

ตัวอย่างนี้จาก MSDNแสดงวิธีใช้ตัวเลขที่ซับซ้อนและให้พวกมันใช้ตัวดำเนินการ + ปกติ

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


0

การใช้งานเกินพิกัดที่ดีนั้นหาได้ยาก แต่ก็เกิดขึ้นได้

โอเปอเรเตอร์ที่บรรทุกเกินพิกัด == และโอเปอเรเตอร์ = แสดงสองโรงเรียนแห่งความคิด: สำหรับการพูดมันทำให้สิ่งต่าง ๆ ง่ายขึ้นและผู้ที่พูดว่ามันป้องกันการเปรียบเทียบที่อยู่ (เช่นฉันกำลังชี้ไปยังสถานที่เดียวกัน วัตถุ).

ฉันพบว่าตัวดำเนินการโอเวอร์โหลดมีประโยชน์ในสถานการณ์เฉพาะ ตัวอย่างเช่นฉันต้องทำให้เป็นอันดับ / deserialize ใน XML บูลีนแสดงเป็น 0 หรือ 1 ขวา (โดยนัยหรือชัดเจนฉันลืม) ผู้ประกอบการโยนจากบูลีนเพื่อ int และกลับได้หลอกลวง


4
มันไม่ได้ป้องกันการเปรียบเทียบที่อยู่: คุณยังสามารถใช้งานobject.ReferenceEquals()ได้
dan04

@ dan04 ดีมากที่รู้!
MPelletier

อีกวิธีหนึ่งในการเปรียบเทียบที่อยู่คือบังคับให้ใช้วัตถุ==โดยการส่งแบบหล่อ: (object)foo == (object)barเปรียบเทียบการอ้างอิงเสมอ แต่ฉันชอบReferenceEquals()มากกว่าที่ @ dan04 พูดถึงเพราะมันชัดเจนว่ามันทำอะไร
svick

0

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

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

ในฐานะที่เป็นตัวอย่างนอก C # ภาษา Python รวมถึงพฤติกรรมพิเศษมากมายที่นำมาใช้เป็นตัวดำเนินการที่มากเกินไป สิ่งเหล่านี้รวมถึงinโอเปอเรเตอร์สำหรับการทดสอบการเป็นสมาชิก()ผู้ปฏิบัติงานสำหรับการโทรหาวัตถุราวกับว่ามันเป็นฟังก์ชั่นและlenผู้ประกอบการสำหรับการกำหนดความยาวหรือขนาดของวัตถุ

และจากนั้นคุณมีภาษาเช่น Haskell, Scala และภาษาอื่น ๆ อีกมากมายที่ชื่อเช่น+นั้นเป็นเพียงแค่ฟังก์ชั่นธรรมดาไม่ใช่ตัวดำเนินการเลย


0

จุดโครงสร้างในSystem.Drawing namespace ใช้การบรรทุกเกินพิกัดเพื่อเปรียบเทียบสองสถานที่ที่แตกต่างกันมากไปใช้ประกอบการ

 Point locationA = new Point( 50, 50 );
 Point locationB = new Point( 50, 50 );

 if ( locationA == locationB )
    Console.WriteLine( "Their locations are the same" );
 else
    Console.WriteLine( "Their locations  are different" );

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


0

หากคุณคุ้นเคยกับเวกเตอร์ทางคณิตศาสตร์คุณอาจเห็นการใช้งานในการบรรทุกตัว+ดำเนินการมากเกินไป คุณสามารถเพิ่มเวกเตอร์a=[1,3]ด้วยและได้รับb=[2,-1]c=[3,2]

การโหลดมากเกินไปเท่ากับ (==) อาจมีประโยชน์ (แม้ว่ามันอาจจะดีกว่าที่จะใช้equals()วิธีการ) หากต้องการทำตัวอย่างเวกเตอร์ต่อ:

v1=[1,3]
v2=[1,3]
v1==v2 // True

-2

ลองนึกภาพชิ้นส่วนของรหัสสำหรับการวาดภาพบนแบบฟอร์ม

{
  Point p = textBox1.Location;
  Size dp = textBox1.Size;

  // Here the + operator has been overloaded by the CLR
  p += dp;  // Now p points to the lower right corner of the textbox.
  ..
}

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

public struct Pos
{
    public double x, y, z;
    public double Distance { get { return Math.Sqrt(x * x + y * y + z * z); } }
    public static Pos operator +(Pos A, Pos B)
    {
        return new Pos() { x = A.x + B.x, y = A.y + B.y, z = A.z + B.z };
    }
    public static Pos operator -(Pos A, Pos B)
    {
        return new Pos() { x = A.x - B.x, y = A.y - B.y, z = A.z - B.z };
    }
}

เพื่อใช้ในภายหลังเท่านั้น

{
    Pos A = new Pos() { x = 4, y = -1, z = 0.5 };
    Pos B = new Pos() { x = 8, y = 2, z = 1.5 };

    double x = (B - A).Distance;
}

4
คุณเพิ่มพาหะไม่ใช่ตำแหน่ง: \ นี่เป็นตัวอย่างที่ดีของเวลาที่ไม่operator+ควรโหลดมากเกินไป(คุณสามารถใช้จุดในแง่ของเวกเตอร์ได้ แต่คุณไม่ควรเพิ่มจุดสองจุด)
BlueRaja - Danny Pflughoeft

@ BlueRaja-DannyPflughoeft: การเพิ่มตำแหน่งให้ผลผลิตตำแหน่งไม่ได้ทำให้รู้สึกอื่น แต่การลบพวกเขา (ให้ผลผลิตเวกเตอร์) ไม่เป็นไม่เฉลี่ยพวกเขา เราสามารถคำนวณค่าเฉลี่ยของ p1, p2, p3 และ p4 ผ่านp1+((p2-p1)+(p3-p1)+(p4-p1))/4ได้ แต่ดูเหมือนว่าจะค่อนข้างอึดอัด
supercat

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