คุณพบข้อดีของวิธีการขยายอะไรบ้าง? [ปิด]


84

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

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

มีสถานการณ์ใดบ้างที่คุณเคยใช้เมธอดส่วนขยายที่คุณไม่มี (หรือไม่ควรมี) โดยใช้เมธอดที่เพิ่มในคลาสของคุณเอง


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

@DanTao ในบันทึกย่อสิ่งหนึ่งที่ทำให้การโทรด้วยวิธีการขยายไม่สามารถถูกแทนที่ได้คือการตั้งชื่อของพวกเขา Extensions.To(1, 10)ไม่มีความหมายและ1.To(10)เป็นคำอธิบาย แน่นอนฉันเข้าใจด้านเทคนิคที่คุณกำลังพูดถึงเพียงแค่พูด ในความเป็นจริงมีสถานการณ์ที่หนึ่ง " ไม่สามารถมี" dynamicวิธีการขยายสินค้าในสถานที่ของวิธีการคงสะท้อนเช่นกรณีอื่น
nawfal

คำตอบ:


35

ฉันคิดว่าวิธีการส่วนขยายช่วยได้มากในการเขียนโค้ดหากคุณเพิ่มวิธีการขยายเป็นประเภทพื้นฐานคุณจะได้รับสิ่งเหล่านี้อย่างรวดเร็วใน Intellisense

ฉันมีให้บริการในรูปแบบการจัดรูปแบบขนาดไฟล์ ในการใช้งานฉันต้องเขียน:

Console.WriteLine(String.Format(new FileSizeFormatProvider(), "{0:fs}", fileSize));

การสร้างวิธีการขยายฉันสามารถเขียน:

Console.WriteLine(fileSize.ToFileSize());

สะอาดและเรียบง่ายขึ้น


4
ชื่อที่สื่อความหมายมากขึ้นสำหรับวิธีการขยายของคุณจะทำให้มันดูสะอาดขึ้น เช่น fileSize ToFormattedString ();
David Alpert

1
mmm, ToFormattedFizeSize () อาจเป็นวิธีการขยายสำหรับประเภทยาว ToFormattedString () ไม่ใช่ชื่อที่สื่อความหมาย
Eduardo Campañó

3
จุดทำแม้ว่า เมื่อฉันอ่าน fileSize ToFileSize () ฉันถามว่าทำไมต้องทำซ้ำ มันทำอะไร? ฉันรู้ว่ามันเป็นเพียงตัวอย่าง แต่ชื่อที่สื่อความหมายช่วยทำให้มันดูสะอาดตาและเรียบง่าย
David Alpert

1
คุณถูกต้องเช่นเดียวกับการทดสอบการเลือกชื่อที่ถูกต้องเป็นสิ่งสำคัญมาก
Eduardo Campañó

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

83

เพียงประโยชน์จากวิธีการขยายเป็นรหัสการอ่าน แค่นั้นแหละ.

วิธีการขยายช่วยให้คุณทำสิ่งนี้ได้:

foo.bar();

แทนสิ่งนี้:

Util.bar(foo);

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

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


1
สิ่งนี้จะสำคัญยิ่งขึ้นหากคุณใช้วิธีการล่ามโซ่เช่น x.Foo (บางอย่าง) .Bar (somethingelse) .Baz (yetmoresomething) นี่เป็นวิธีที่ดีที่สุดในวิธีการขยาย IEnumerable <> และเพิ่มความสามารถในการอ่านอย่างมีนัยสำคัญ
ShuggyCoUk

2
สิ่งที่ฉันไม่ได้รับคือ foo.bar () แนะนำฉันว่า bar () เป็นวิธีการของ foo ดังนั้นเมื่อมันไม่ได้ผลฉันก็ต้องมองหาในที่ที่ไม่ถูกต้อง ฉันไม่แน่ใจว่านี่เป็นการปรับปรุงการอ่านสำหรับฉันจริงๆ
Martin Brown

3
คลิกขวาที่แถบ () ใน VS IDE แล้วเลือกไปที่คำจำกัดความจะนำคุณไปยังตำแหน่งที่ถูกต้อง
Jeff Martin

9
ข้อดีประการเดียวของโครงสร้างภาษา 98% คือความสามารถในการอ่านโค้ด อะไรก็ตามที่ผ่านมาเป็นผู้ดำเนินการสาขาป้ายกำกับ goto และส่วนเพิ่มจะช่วยให้ชีวิตของคุณง่ายขึ้นเล็กน้อย
Giovanni Galbo

2
ไม่เป็นความจริงทั้งหมดมีบางสถานการณ์ที่วิธีการขยายเป็นวิธีเดียวในการขยายชั้นเรียน นอกจากคลาสปิดผนึกแล้วฉันยังใช้มันในสถานการณ์ที่ไม่เหมือนใคร ดูโพสต์ของฉันด้านล่าง
Edwin Jarvis

34

อย่าลืมเครื่องมือ! เมื่อคุณเพิ่มวิธีการขยาย M ในประเภท Foo คุณจะได้รับ 'M' ในรายการ intellisense ของ Foo (สมมติว่าคลาสส่วนขยายอยู่ในขอบเขต) สิ่งนี้ทำให้ 'M' ค้นหาได้ง่ายกว่า MyClass.M (Foo, ... )

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


2
นอกจากนี้ยังมีข้อเสียเล็กน้อยสำหรับสิ่งนี้: วิธีการขยาย (เช่นจาก System.Linq เติม IntelliSense อัตโนมัติด้วยรายการที่ยาวขึ้นทำให้นำทางยากขึ้นเล็กน้อย
Jared Updike

@ จาเร็ด: ... แต่ถ้าคุณนำเข้าเนมสเปซที่มีวิธีการขยายอยู่เท่านั้นดังนั้นคุณจึงสามารถควบคุม "มลพิษจาก IntelliSense" ได้ในระดับเล็กน้อย ประการที่สองอย่าลืมว่ามีคลาสใน BCL (เช่นString) ที่มาพร้อมกับรายการวิธีการอินสแตนซ์ที่ค่อนข้างยาวดังนั้นปัญหานี้จึงไม่เฉพาะเจาะจงสำหรับวิธีการขยาย
stakx - ไม่ร่วมให้ข้อมูลอีกต่อไป

29

ประโยชน์อีกสองประการของวิธีการขยายที่ฉันพบ:

  • อินเทอร์เฟซที่คล่องแคล่วสามารถห่อหุ้มด้วยวิธีการขยายคลาสแบบคงที่ได้ดังนั้นจึงสามารถแยกความกังวลระหว่างคลาสหลักและส่วนขยายได้อย่างคล่องแคล่ว ฉันเห็นว่าสามารถบำรุงรักษาได้ดีกว่า
  • คุณสามารถแขวนวิธีการต่อขยายออกจากอินเทอร์เฟซได้ซึ่งจะช่วยให้คุณสามารถระบุสัญญา (ผ่านอินเทอร์เฟซ) และชุดพฤติกรรมที่ใช้อินเทอร์เฟซที่เกี่ยวข้อง (ผ่านวิธีการขยาย) อีกครั้งเพื่อแยกข้อกังวล ตัวอย่างมีนามสกุล Linq วิธีการชอบSelect(...), Where(...)ฯลฯ ฮุปิดIEnumerable<T>อินเตอร์เฟซ

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

ใช่ตัวอย่างสำหรับจุดที่ 2 ก็น่าจะดี
Ini

26

การใช้งานที่ดีที่สุดบางอย่างที่ฉันมีสำหรับวิธีการขยายคือความสามารถในการ:

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

ยกตัวอย่างเช่นIEnumerable<T>. แม้ว่าจะมีวิธีการขยายมากมาย แต่ฉันพบว่ามันน่ารำคาญที่ไม่ได้ใช้วิธีการทั่วไปสำหรับแต่ละวิธี ฉันจึงสร้างของตัวเอง:

public void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
{
    foreach ( var o in enumerable )
    {
        action(o);
    }
}

Voila IEnumerable<T>วัตถุทั้งหมดของฉันโดยไม่คำนึงถึงประเภทการนำไปใช้และไม่ว่าฉันจะเขียนมันหรือไม่ก็ตามตอนนี้คนอื่นมีForEachวิธีการโดยการเพิ่มคำสั่ง "ใช้" ที่เหมาะสมในโค้ดของฉัน


3
+1 ฉันชอบส่วนขยายนี้ฉันเขียนแบบเดียวกับมัน มีประโยชน์มาก
Maslow

10
โปรดทราบว่าวิธีการนี้ต้องเป็นแบบคงที่ ฉันขอแนะนำให้อ่านblogs.msdn.com/ericlippert/archive/2009/05/18/…ก่อนตัดสินใจใช้ส่วนขยายนี้
TrueWill

เหตุผลที่ใช้วิธีการขยาย ForEach บน IEnumerable ไม่ได้รับความนิยม - blogs.msdn.microsoft.com/ericlippert/2009/05/18/…
RBT

12

หนึ่งในเหตุผลที่ดีในการใช้วิธีการขยายคือ LINQ หากไม่มีวิธีการขยายสิ่งที่คุณสามารถทำได้ใน LINQ จะเป็นเรื่องยากมาก Where (), ประกอบด้วย (), Select extension method หมายถึงการเพิ่มฟังก์ชันการทำงานจำนวนมากให้กับชนิดที่มีอยู่โดยไม่ต้องเปลี่ยนโครงสร้าง


4
ไม่ใช่ 'หนึ่งใน' เหตุผลของวิธีการขยาย
gbjbaanb

3
ยังไง? เป็นหนึ่งในหลายสาเหตุที่ต้องมีวิธีการขยายไม่ใช่ข้อกำหนดเพียงอย่างเดียว ฉันสามารถใช้วิธีการขยายโดยไม่ต้องใช้ LINQ
Ray Booysen

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

9

มีคำตอบมากมายเกี่ยวกับข้อดีของวิธีการขยาย แล้วเราจะจัดการกับข้อเสียได้อย่างไร?

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

สมมติว่าคุณสร้างวิธีการขยายที่ใช้กับคลาสใดคลาสหนึ่ง จากนั้นมีคนสร้างเมธอดที่มีลายเซ็นเหมือนกันในคลาสนั้นเอง

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


1
4. คิดว่าสองครั้งก่อนที่จะขยายประเภทคุณทำไม่ได้เอง หากคุณเขียนเฉพาะวิธีการขยายสำหรับประเภทที่คุณเป็นเจ้าของคุณก็ไม่ต้องกังวลว่าส่วนขยายของคุณจะเสียหายจากการเปลี่ยนแปลงประเภทที่ขยายออกไป ในทางกลับกันถ้าคุณให้คนประเภทอื่นที่ไม่ใช่คุณเป็นหลักด้วยความเมตตาของพวกเขา
DavidRR

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

วิธีการขยาย (คู่มือการเขียนโปรแกรม C #) : โดยทั่วไปคุณอาจจะเรียกใช้เมธอดส่วนขยายบ่อยกว่าการใช้งานของคุณเอง
DavidRR


4

ข้อโต้แย้งส่วนตัวของฉันสำหรับวิธีการขยายคือมันเข้ากันได้ดีกับการออกแบบ OOP: พิจารณาวิธีง่ายๆ

bool empty = String.IsNullOrEmpty (myString)

เมื่อเปรียบเทียบกับ

bool empty = myString.IsNullOrEmpty ();

ฉันไม่เห็นว่าตัวอย่างของคุณเกี่ยวข้องกับ OOP คุณหมายถึงอะไร?
Andrew Hare

1
ดูเหมือนว่าเมธอดจะเป็นของวัตถุ
Bluenuance

3
นี่เป็นตัวอย่างที่ไม่ดี (สามารถโยน NullReferenceException) แต่เขามาถูกทางแล้ว ตัวอย่างที่ดีกว่าอาจจะแทนที่ Math.abs (-4) ด้วยบางอย่างเช่น -4.abs ();
Outlaw Programmer

15
สำหรับความรู้ของฉัน (และจากประสบการณ์ของฉันหากหน่วยความจำทำหน้าที่) myString.IsNullOrEmpty () ไม่จำเป็นต้องโยน NullReferenceException เนื่องจากวิธีการส่วนขยายไม่ต้องการอินสแตนซ์วัตถุเพื่อที่จะเริ่มทำงาน
David Alpert

1
โดยส่วนตัวแล้วฉันไม่ใช่แฟนตัวยงของวิธีการขยายที่มีจุดประสงค์เพื่อใช้งานกับอินสแตนซ์ว่าง - ดูเหมือนว่าผู้อ่านจะเป็นสิ่งหนึ่ง แต่ก็ทำอย่างอื่น
Mark Simpson

4

มีคำตอบที่ดีมากมายข้างต้นเกี่ยวกับวิธีการขยายที่ช่วยให้คุณทำได้

คำตอบสั้น ๆ ของฉันคือ - เกือบจะไม่จำเป็นต้องมีโรงงาน

ฉันจะชี้ให้เห็นว่าพวกเขาไม่ใช่แนวคิดใหม่และหนึ่งในการตรวจสอบความถูกต้องที่ใหญ่ที่สุดของพวกเขาคือพวกเขาเป็นคุณสมบัติที่น่ากลัวใน Objective-C ( ประเภท ) พวกเขาเพิ่มความยืดหยุ่นอย่างมากให้กับการพัฒนาตามกรอบงานซึ่ง NeXT มี NSA และ Wall Street ผู้สร้างโมเดลทางการเงินเป็นผู้ใช้รายใหญ่

REALbasic ยังนำมาใช้เป็นวิธีการที่ขยายออกไปและมีการใช้งานที่คล้ายคลึงกันทำให้การพัฒนาง่ายขึ้น


4

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


ลองใช้แบบสอบถาม LINQ นี้เป็นตัวอย่าง:

numbers.Where(x => x > 0).Select(x => -x)

ทั้งสองWhereและวิธีการขยายกำหนดไว้ในระดับคงที่Select Enumerableดังนั้นหากไม่มีวิธีการขยายและนี่เป็นวิธีการคงที่ปกติบรรทัดสุดท้ายของโค้ดจะต้องมีลักษณะดังนี้:

Enumerable.Select(Enumerable.Where(numbers, x => x > 0), x => -x)

ดูว่าข้อความค้นหานั้นน่ากลัวมากแค่ไหน


ประการที่สองหากตอนนี้คุณต้องการแนะนำตัวดำเนินการสืบค้นของคุณเองคุณจะไม่มีทางกำหนดมันภายในEnumerableคลาสแบบคงที่ได้เช่นเดียวกับตัวดำเนินการแบบสอบถามมาตรฐานอื่น ๆ ทั้งหมดเนื่องจากEnumerableอยู่ในกรอบและคุณไม่สามารถควบคุมคลาสนั้นได้ ดังนั้นคุณจะต้องกำหนดคลาสคงที่ของคุณเองที่มีวิธีการขยาย จากนั้นคุณอาจได้รับคำค้นหาเช่นนี้:

Enumerable.Select(MyEnumerableExtensions.RemoveNegativeNumbers(numbers), x => -x)
//                ^^^^^^^^^^^^^^^^^^^^^^
//                different class name that has zero informational value
//                and, as with 'Enumerable.xxxxxx', only obstructs the
//                query's actual meaning.

3

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


3

วิธีการขยายยังสามารถช่วยให้ชั้นเรียนและการอ้างอิงชั้นเรียนของคุณสะอาด ตัวอย่างเช่นคุณอาจต้องใช้เมธอด Bar () สำหรับคลาส Foo ทุกที่ที่ใช้ Foo อย่างไรก็ตามคุณอาจต้องการเมธอด. ToXml () ในแอสเซมบลีอื่นและสำหรับแอสเซมบลีนั้นเท่านั้น ในกรณีนั้นคุณสามารถเพิ่มการอ้างอิง System.Xml และ / หรือ System.Xml.Linq ที่จำเป็นในแอสเซมบลีนั้นและไม่ใช่ในแอสเซมบลีดั้งเดิม

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


3

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

IMO โดยใช้วิธีการขยายเพื่อเพิ่มพฤติกรรมในชั้นเรียนของคุณสามารถ:

สับสน: โปรแกรมเมอร์อาจเชื่อว่าเมธอดเป็นส่วนหนึ่งของประเภทขยายดังนั้นจึงไม่เข้าใจว่าเหตุใดวิธีการจึงหายไปเมื่อไม่ได้นำเข้าส่วนขยายเนมสเปซ

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


ไม่เป็นความจริงที่นักพัฒนาติดอยู่กับวิธีการมากมายที่เขาไม่สามารถปลอมได้ ในที่สุดวิธีการส่วนขยายเหล่านี้จะเรียกวิธีการบางอย่างบนคลาส / อินเทอร์เฟซที่กำลังขยายและวิธีนี้สามารถถูกดักจับได้เนื่องจากเป็นเสมือน
Patrik Hägne

1
ถ้าอย่างนั้นมันขึ้นอยู่กับสิ่งที่ทำในวิธีการขยาย สมมติว่าคุณใช้วิธีการขยายที่มีให้บนอินเทอร์เฟซโดยไม่รู้ด้วยซ้ำว่าเป็นวิธีการขยาย จากนั้นคุณจะไม่ปลอมการเรียกเมธอดไปยังอินเทอร์เฟซ แต่คุณตระหนักดีว่าคุณกำลังเรียกใช้เมธอดแบบคงที่ การเรียกใช้เมธอดแบบคงที่ไม่สามารถดักจับได้โดย API ปลอมที่ใช้พร็อกซีดังนั้นทางเลือกเดียวของคุณคือการสะท้อนวิธีการขยายเพื่อดูว่าวิธีใดที่จะปลอมได้จริง ยังคงต่อต้านรูปแบบเมื่อรวมกับ IMO การทดสอบหน่วย
HaraldV

2

วิธีการขยายเป็นการรวมตัวของ. NET ของตัวอ้างอิง"แนะนำวิธีการต่างประเทศ"จากหนังสือของ Martin Fowler (ลงไปจนถึงลายเซ็นของวิธีการ) พวกเขามาพร้อมกับผลประโยชน์และข้อผิดพลาดเดียวกัน ในส่วนของ refactor นี้เขาบอกว่ามันเป็นวิธีแก้ปัญหาเมื่อคุณไม่สามารถแก้ไขคลาสที่ควรเป็นเจ้าของเมธอดได้จริงๆ


2
+1 แต่โปรดทราบว่าคุณสามารถใช้วิธีการขยายกับอินเทอร์เฟซได้เช่นกัน
TrueWill

2

ส่วนใหญ่ฉันเห็นวิธีการขยายเป็นการยอมรับว่าบางทีพวกเขาไม่ควรไม่อนุญาตฟังก์ชันฟรี

ในชุมชน C ++ มักถูกมองว่าเป็นแนวทางปฏิบัติ OOP ที่ดีในการเลือกใช้ฟังก์ชันที่ไม่ใช่สมาชิกฟรีมากกว่าสมาชิกเนื่องจากฟังก์ชันเหล่านี้ไม่ทำลายการห่อหุ้มโดยการเข้าถึงสมาชิกส่วนตัวที่พวกเขาไม่ต้องการ วิธีการขยายดูเหมือนจะเป็นวิธีอ้อมเพื่อให้บรรลุสิ่งเดียวกัน นั่นคือไวยากรณ์ที่สะอาดกว่าสำหรับฟังก์ชันคงที่ซึ่งไม่สามารถเข้าถึงสมาชิกส่วนตัวได้

วิธีการขยายนั้นไม่มีอะไรมากไปกว่าน้ำตาลในการสังเคราะห์ แต่ฉันไม่เห็นอันตรายใด ๆ ในการใช้


2
  • Intellisense บนวัตถุเองแทนที่จะต้องเรียกใช้ฟังก์ชันยูทิลิตี้ที่น่าเกลียดบางอย่าง
  • สำหรับฟังก์ชั่นการแปลงสามารถเปลี่ยน "XToY (X x)" เป็น "ToY (this X x)" ซึ่งส่งผลให้ x ToY () ค่อนข้างน่าเกลียดแทน XToY (x)
  • ขยายชั้นเรียนที่คุณไม่สามารถควบคุมได้
  • ขยายฟังก์ชันการทำงานของคลาสเมื่อไม่ต้องการเพิ่มเมธอดให้กับคลาสด้วยตนเอง ตัวอย่างเช่นคุณสามารถทำให้ออบเจ็กต์ทางธุรกิจเรียบง่ายและปราศจากตรรกะและเพิ่มตรรกะทางธุรกิจที่เฉพาะเจาะจงด้วยการอ้างอิงที่น่าเกลียดในวิธีการขยาย

2

ฉันใช้มันเพื่อใช้คลาสโมเดลอ็อบเจ็กต์ของฉันซ้ำ ฉันมีคลาสมากมายที่แสดงถึงออบเจ็กต์ที่ฉันมีในฐานข้อมูล คลาสเหล่านี้ใช้ในฝั่งไคลเอ็นต์เท่านั้นเพื่อแสดงอ็อบเจ็กต์ดังนั้นการใช้งานพื้นฐานคือการเข้าถึงคุณสมบัติ

public class Stock {
   public Code { get; private set; }
   public Name { get; private set; }
}

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

public static class StockExtender {
    public static List <Quote> GetQuotesByDate(this Stock s, DateTime date)
    {...}
}

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

สิ่งหนึ่งที่น่าสนใจเกี่ยวกับโซลูชันนี้ก็คือคลาสโมเดลอ็อบเจ็กต์ของฉันสร้างแบบไดนามิกโดยใช้Mono.Cecilดังนั้นจึงเป็นการยากมากที่จะเพิ่มวิธีตรรกะทางธุรกิจแม้ว่าฉันจะต้องการก็ตาม ฉันมีคอมไพเลอร์ที่อ่านไฟล์นิยาม XML และสร้างคลาสสตับเหล่านี้แทนอ็อบเจ็กต์บางอย่างที่ฉันมีในฐานข้อมูล แนวทางเดียวในกรณีนี้คือการขยายความ


1
คุณสามารถใช้คลาสบางส่วนสำหรับสิ่งนี้ ...
JJoos

1

ช่วยให้ C # รองรับภาษาไดนามิก LINQ และอื่น ๆ อีกมากมาย ตรวจสอบบทความสกอตต์ของ Guthrie


1

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


1

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

วิธีการขยายอนุญาตให้ใช้เพื่อเพิ่มฟังก์ชันการทำงาน (บนฝั่งไคลเอ็นต์) ให้กับชนิดที่ส่งคืนโดยวิธีการทางเว็บโดยไม่ต้องสร้างโมเดลอ็อบเจ็กต์อื่นหรือคลาส wrapper จำนวนมากบนฝั่งไคลเอ็นต์


1

วิธีการขยายสามารถใช้เพื่อสร้างmixin ชนิดหนึ่งใน C #

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

นอกจากนี้ยังสามารถนำมาใช้เพื่อช่วยให้มีบทบาทสำคัญใน C #, แนวคิดศูนย์กลางของสถาปัตยกรรม DCI


1

โปรดจำไว้ว่ามีการเพิ่มวิธีการส่วนขยายเพื่อช่วยให้การสืบค้น Linq อ่านง่ายขึ้นเมื่อใช้ในรูปแบบ C #

ผลกระทบ 2 รายการนี้เทียบเท่ากันอย่างแน่นอน แต่อย่างแรกนั้นอ่านได้ไกลกว่ามาก (และช่องว่างในการอ่านจะเพิ่มขึ้นด้วยวิธีการอื่น ๆ ที่ถูกล่ามโซ่)

int n1 = new List<int> {1,2,3}.Where(i => i % 2 != 0).Last();

int n2 = Enumerable.Last(Enumerable.Where(new List<int> {1,2,3}, i => i % 2 != 0));

โปรดทราบว่าไวยากรณ์ที่มีคุณสมบัติครบถ้วนควรเป็น:

int n1 = new List<int> {1,2,3}.Where<int>(i => i % 2 != 0).Last<int>();

int n2 = Enumerable.Last<int>(Enumerable.Where<int>(new List<int> {1,2,3}, i => i % 2 != 0));

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

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

โดยเฉพาะอย่างยิ่งมันเป็นวิธีที่สง่างามและน่าเชื่อถือกว่าที่ฉันพบว่ามีเมธอดคลาสพื้นฐานที่เรียกใช้โดยคลาสย่อยใด ๆ และส่งคืนการอ้างอิงที่พิมพ์อย่างรุนแรงไปยังคลาสย่อยนี้ (ด้วยประเภทคลาสย่อย)

ตัวอย่าง (โอเคสถานการณ์นี้ช่างวิเศษสุด ๆ ): หลังจากหลับฝันดีสัตว์ชนิดหนึ่งลืมตาแล้วส่งเสียงร้อง สัตว์ทุกตัวลืมตาเหมือนกันในขณะที่สุนัขเห่าและเป็ดกวัก

public abstract class Animal
{
    //some code common to all animals
}

public static class AnimalExtension
{
    public static TAnimal OpenTheEyes<TAnimal>(this TAnimal animal) where TAnimal : Animal
    {
        //Some code to flutter one's eyelashes and then open wide
        return animal; //returning a self reference to allow method chaining
    }
}

public class Dog : Animal
{
    public void Bark() { /* ... */ }
}

public class Duck : Animal
{
    public void Kwak() { /* ... */ }
}

class Program
{
    static void Main(string[] args)
    {
        Dog Goofy = new Dog();
        Duck Donald = new Duck();
        Goofy.OpenTheEyes().Bark(); //*1
        Donald.OpenTheEyes().Kwak(); //*2
    }
}

แนวคิดOpenTheEyesควรจะเป็นAnimalวิธีการ แต่มันก็จะกลับมาอินสแตนซ์ของระดับนามธรรมAnimalซึ่งไม่ทราบวิธีการที่เฉพาะเจาะจงเช่น subclass BarkหรือDuckหรืออะไรก็ตาม 2 บรรทัดที่แสดงความคิดเห็นเป็น * 1 และ * 2 จะทำให้เกิดข้อผิดพลาดในการคอมไพล์

แต่ด้วยวิธีการขยายเราสามารถมี "เมธอดพื้นฐานที่รู้ประเภทคลาสย่อยที่เรียกว่า"

โปรดทราบว่าวิธีการทั่วไปง่ายๆสามารถทำงานได้ แต่ในวิธีที่น่าอึดอัดใจกว่า:

public abstract class Animal
{
    //some code common to all animals

    public TAnimal OpenTheEyes<TAnimal>() where TAnimal : Animal
    {
        //Some code to flutter one's eyelashes and then open wide
        return (TAnimal)this; //returning a self reference to allow method chaining
    }
}

คราวนี้ไม่มีพารามิเตอร์จึงไม่มีการอนุมานประเภทการส่งคืนที่เป็นไปได้ การโทรจะเป็นอะไรไปไม่ได้นอกจาก:

Goofy.OpenTheEyes<Dog>().Bark();
Donald.OpenTheEyes<Duck>().Kwak();

... ซึ่งสามารถชั่งน้ำหนักรหัสได้มากหากมีการผูกมัดมากขึ้น (โดยเฉพาะอย่างยิ่งเมื่อรู้ว่าพารามิเตอร์ type จะอยู่<Dog>ในบรรทัดของ Goofy และ<Duck>ของ Donald ... )


1
ครั้งแรกที่ฉันเคยเห็น "กวัก" นี่เป็นการสะกดที่ใช้กันทั่วไปในประเทศอื่นหรือไม่? วิธีเดียวที่ฉันเคยเห็นมันสะกดคือ "ต้มตุ๋น"
Brent Rittenhouse

0

ฉันมีเพียงคำเดียวที่จะบอกเกี่ยวกับเรื่องนี้: การบำรุงรักษานี่คือกุญแจสำคัญสำหรับการใช้วิธีการขยาย


4
ฉันคิดว่าคุณอาจต้องการมากกว่าหนึ่งคำเพราะฉันไม่เข้าใจความหมาย
Outlaw Programmer

2
ที่จริงผมจะบอกว่าเป็นข้อโต้แย้งกับวิธีการขยาย ความเสี่ยงอย่างหนึ่งของวิธีการขยายคือสามารถเกิดขึ้นได้ทุกที่ทุกคนสร้างขึ้น Wildgrow สามารถเกิดขึ้นได้หากไม่ใช้อย่างระมัดระวัง
Boris Callens

คุณสามารถค้นหาข้อมูลอ้างอิงทั้งหมดได้อย่างง่ายดายเมื่อคุณใช้ EM นอกจากนี้ในการเปลี่ยนวิธีการเดียวทั่วโลกคุณสามารถทำได้จากที่เดียวเท่านั้น
Tamir

0

ฉันคิดว่าวิธีการขยายช่วยในการเขียนโค้ดที่ชัดเจนขึ้น

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

ฉันรู้สึกว่าวิธีการขยายทำให้โค้ดของคุณชัดเจนและจัดระเบียบได้อย่างเหมาะสมมากขึ้น



0

ฉันรักพวกเขาในการสร้าง html บ่อยครั้งที่มีส่วนที่ใช้ซ้ำ ๆ กันหรือสร้างขึ้นซ้ำ ๆ โดยที่ฟังก์ชันมีประโยชน์ แต่จะทำลายขั้นตอนของโปรแกรม

        HTML_Out.Append("<ul>");
        foreach (var i in items)
            if (i.Description != "")
            {
                HTML_Out.Append("<li>")
                    .AppendAnchor(new string[]{ urlRoot, i.Description_Norm }, i.Description)
                    .Append("<div>")
                    .AppendImage(iconDir, i.Icon, i.Description)
                    .Append(i.Categories.ToHTML(i.Description_Norm, urlRoot)).Append("</div></li>");
            }

        return HTML_Out.Append("</ul>").ToString();

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


0

ฉันพบว่าเมธอดส่วนขยายมีประโยชน์ในการจับคู่อาร์กิวเมนต์ทั่วไปที่ซ้อนกัน

ฟังดูแปลก ๆ - แต่บอกว่าเรามีคลาสทั่วไปMyGenericClass<TList>และเรารู้ว่า TList นั้นเป็นแบบทั่วไป (เช่น a List<T>) ฉันไม่คิดว่าจะมีวิธีขุด 'T' ที่ซ้อนกันออกจากรายการโดยไม่มีส่วนขยาย วิธีการหรือวิธีการช่วยเหลือแบบคงที่ หากเรามีเพียงวิธีการช่วยเหลือแบบคงที่เท่านั้นมันจะ (a) น่าเกลียดและ (b) จะบังคับให้เราย้ายฟังก์ชันที่อยู่ในคลาสไปยังตำแหน่งภายนอก

เช่นเพื่อดึงข้อมูลประเภทในทูเพิลและแปลงเป็นลายเซ็นวิธีการเราสามารถใช้วิธีการขยาย:

public class Tuple { }
public class Tuple<T0> : Tuple { }
public class Tuple<T0, T1> : Tuple<T0> { }

public class Caller<TTuple> where TTuple : Tuple { /* ... */ }

public static class CallerExtensions
{
     public static void Call<T0>(this Caller<Tuple<T0>> caller, T0 p0) { /* ... */ }

     public static void Call<T0, T1>(this Caller<Tuple<T0, T1>> caller, T0 p0, T1 p1) { /* ... */ }
}

new Caller<Tuple<int>>().Call(10);
new Caller<Tuple<string, int>>().Call("Hello", 10);

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


0

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

บางทีการขึ้นไปในลำดับมรดกฉันอาจพบบรรพบุรุษร่วมกันเช่น WebControl พูด แต่ฉันไม่ได้พัฒนา WebControl คลาสเฟรมเวิร์กและไม่เปิดเผยสิ่งที่ฉันต้องการ

ด้วยวิธีการขยายฉันสามารถ:

1) ขยายคลาส WebControl จากนั้นรับพฤติกรรมมาตรฐานแบบรวมของฉันในคลาสอินพุตทั้งหมดของฉัน

2) หรือทำให้คลาสทั้งหมดของฉันได้มาจากอินเทอร์เฟซพูด IInputZone และขยายอินเทอร์เฟซนี้ด้วยวิธีการ ตอนนี้ฉันจะสามารถเรียกวิธีการส่วนขยายที่เกี่ยวข้องกับอินเทอร์เฟซในโซนอินพุตทั้งหมดของฉันได้ ดังนั้นฉันจึงได้รับมรดกหลายประเภทเนื่องจากโซนอินพุตของฉันได้มาจากคลาสพื้นฐานหลายคลาสแล้ว


0

มีตัวอย่างวิธีการขยายที่ยอดเยี่ยมมากมาย.. โดยเฉพาะใน IEnumerables ตามที่โพสต์ไว้ด้านบน

เช่นถ้าฉันมีIEnumerable<myObject>ฉันสามารถสร้างและขยายวิธีการIEnumerable<myObject>

mylist List<myObject>;

... สร้างรายการ

mylist.DisplayInMyWay();

หากไม่มีวิธีการขยายจะต้องโทร:

myDisplayMethod(myOldArray); // can create more nexted brackets.

อีกตัวอย่างที่ดีคือการสร้างรายการที่เชื่อมโยงแบบวงกลมในพริบตา!

ฉันสามารถ 'รับเครดิตได้!

รายชื่อที่เชื่อมโยงแบบวงแหวนโดยใช้วิธีการขยาย

ตอนนี้รวมสิ่งเหล่านี้และใช้รหัสวิธีการส่วนขยายอ่านดังนี้

myNode.NextOrFirst().DisplayInMyWay();

ค่อนข้างมากกว่า

DisplayInMyWay(NextOrFirst(myNode)).

การใช้วิธีการขยายมันดูดีกว่าและอ่านง่ายกว่าและวางแนววัตถุมากขึ้น ยังอยู่ใกล้กับ:

myNode.Next.DoSomething()

แสดงว่าเพื่อนร่วมงานของคุณ! :)

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