เหตุใดฉันจึงควรหลีกเลี่ยงการใช้ Properties ใน C #


102

ในหนังสือที่ยอดเยี่ยมของเขา CLR Via C # เจฟฟรีย์ริชเตอร์กล่าวว่าเขาไม่ชอบคุณสมบัติและไม่แนะนำให้ใช้ เขาให้เหตุผลบางอย่าง แต่ฉันไม่เข้าใจจริงๆ ใครช่วยอธิบายให้ฉันฟังได้ไหมว่าทำไมฉันจึงควรหรือไม่ควรใช้คุณสมบัติ? ใน C # 3.0 ด้วยคุณสมบัติอัตโนมัติสิ่งนี้เปลี่ยนไปหรือไม่?

ในการอ้างอิงฉันได้เพิ่มความคิดเห็นของ Jeffrey Richter:

•คุณสมบัติอาจเป็นแบบอ่านอย่างเดียวหรือเขียนอย่างเดียว การเข้าถึงข้อมูลสามารถอ่านและเขียนได้เสมอ หากคุณกำหนดคุณสมบัติวิธีที่ดีที่สุดคือเสนอทั้ง get และ set accessor method

•วิธีคุณสมบัติอาจทำให้เกิดข้อยกเว้น การเข้าถึงฟิลด์ไม่เคยทำให้เกิดข้อยกเว้น

•คุณสมบัติไม่สามารถส่งผ่านเป็นพารามิเตอร์ out หรือ ref ไปยังเมธอดได้ ฟิลด์สามารถ ตัวอย่างเช่นรหัสต่อไปนี้จะไม่รวบรวม:

using System;
public sealed class SomeType
{
   private static String Name 
   {
     get { return null; }
     set {}
   }
   static void MethodWithOutParam(out String n) { n = null; }
   public static void Main()
   {
      // For the line of code below, the C# compiler emits the following:
      // error CS0206: A property or indexer may not
      // be passed as an out or ref parameter
      MethodWithOutParam(out Name);
   }
}

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

•ถ้าเรียกหลายครั้งติดต่อกันวิธีคุณสมบัติอาจส่งคืนค่าที่แตกต่างกันในแต่ละครั้ง ฟิลด์จะส่งคืนค่าเดียวกันทุกครั้ง คลาส System.DateTime มีคุณสมบัติ readonly Now ที่ส่งคืนวันที่และเวลาปัจจุบัน ทุกครั้งที่คุณค้นหาคุณสมบัตินี้จะส่งคืนค่าที่แตกต่างกัน นี่เป็นข้อผิดพลาดและ Microsoft ต้องการให้พวกเขาแก้ไขคลาสโดยทำให้ Now เป็นวิธีการแทนคุณสมบัติ

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

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


11
ฉันเป็นเจ้าของ 'CLR via C #' ฉบับที่ 3 และในหน้า 242 นายริกเตอร์กล่าวว่าโดยส่วนตัวแล้วเขาไม่ชอบคุณสมบัติ แต่เขาไม่เคยแนะนำให้ไม่ใช้มัน โปรดอ้างอิงรุ่นหนังสือและหมายเลขหน้าที่คุณอ่าน
kirk.burleson

คำตอบ:


173

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

โดยส่วนตัวแล้วฉันไม่เห็นด้วยกับเขาในประเด็นนี้โดยเฉพาะ - ฉันพบว่าคุณสมบัติทำให้รหัสไคลเอนต์อ่านง่ายกว่าการเรียกเมธอดที่เทียบเท่ากันมาก ฉันยอมรับว่านักพัฒนาจำเป็นต้องรู้ว่าคุณสมบัติเป็นวิธีการปลอมตัว แต่ฉันคิดว่าการให้ความรู้แก่นักพัฒนาเกี่ยวกับสิ่งนั้นดีกว่าการทำให้โค้ดอ่านยากขึ้นโดยใช้วิธีการ (โดยเฉพาะอย่างยิ่งเมื่อได้เห็นโค้ด Java ที่มี getters และ setters หลายตัวถูกเรียกในคำสั่งเดียวกันฉันรู้ว่ารหัส C # ที่เท่ากันจะอ่านง่ายกว่ามาก Law of Demeter นั้นดีมากในทางทฤษฎี แต่บางครั้งก็foo.Name.Lengthเป็นเช่นนั้นจริงๆ สิ่งที่ถูกต้องในการใช้ ... )

(และไม่คุณสมบัติที่ใช้งานโดยอัตโนมัติจะไม่เปลี่ยนแปลงสิ่งนี้เลย)

นี่เป็นเหมือนข้อโต้แย้งเล็กน้อยในการใช้วิธีการขยาย - ฉันสามารถเข้าใจเหตุผล แต่ประโยชน์ในทางปฏิบัติ (เมื่อใช้อย่าง จำกัด ) มีมากกว่าข้อเสียในมุมมองของฉัน


ขอบคุณมากสำหรับคำตอบ! เกี่ยวกับการโต้แย้งโดยใช้วิธีการขยาย: คุณกำลังพูดถึงหัวข้อของ Jeffrey Richter หรือเป็นนามธรรม?
abatishchev

@abatishchev: มันเป็นเพียงประเด็นทั่วไปเกี่ยวกับวิธีการขยาย ไม่เกี่ยวข้องโดยตรงกับคุณสมบัติ
Jon Skeet

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

1
@PatrickFromberg: คุณพลาดโค้ดจำนวนมากที่ใช้ฟิลด์แบบอ่านอย่างเดียว ไม่มีอะไรจะบอกว่าคุณสมบัติบ่งบอกถึงความไม่แน่นอน ฉันมักจะมีฟิลด์แบบอ่านอย่างเดียวที่สนับสนุนคุณสมบัติอ่านอย่างเดียว - คุณคิดว่านั่นเป็นสิ่งที่ไม่ดีหรือไม่?
Jon Skeet

1
@Patrick: ไม่ได้นำประโยชน์ของการแยก API ออกจากการนำไปใช้งาน - คุณสามารถเปลี่ยนวิธีคำนวณคุณสมบัติจากฟิลด์ได้ในภายหลัง (เช่นการคำนวณคุณสมบัติที่เกี่ยวข้องสองรายการจากฟิลด์เดียว)
Jon Skeet

34

เรามาโต้แย้งกันทีละคน:

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

นี่เป็นข้อดีสำหรับคุณสมบัติเนื่องจากคุณสามารถควบคุมการเข้าถึงได้ละเอียดมากขึ้น

วิธีคุณสมบัติอาจทำให้เกิดข้อยกเว้น การเข้าถึงฟิลด์ไม่เคยทำให้เกิดข้อยกเว้น

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

•คุณสมบัติไม่สามารถส่งผ่านเป็นพารามิเตอร์ out หรือ ref ไปยังเมธอดได้ ฟิลด์สามารถ

ยุติธรรม.

•วิธีการคุณสมบัติอาจใช้เวลานานในการดำเนินการ การเข้าถึงฟิลด์มักจะเสร็จสมบูรณ์ในทันที

นอกจากนี้ยังสามารถใช้เวลาน้อยมาก

•หากเรียกหลายครั้งติดต่อกันวิธีคุณสมบัติอาจส่งคืนค่าที่แตกต่างกันในแต่ละครั้ง ฟิลด์จะส่งคืนค่าเดียวกันในแต่ละครั้ง

ไม่จริง. คุณจะรู้ได้อย่างไรว่าค่าของฟิลด์ไม่ได้เปลี่ยนแปลง (อาจเกิดจากเธรดอื่น)

คลาส System.DateTime มีคุณสมบัติ readonly Now ที่ส่งคืนวันที่และเวลาปัจจุบัน ทุกครั้งที่คุณค้นหาคุณสมบัตินี้จะส่งคืนค่าที่แตกต่างกัน นี่เป็นข้อผิดพลาดและ Microsoft ต้องการให้พวกเขาแก้ไขคลาสโดยทำให้ Now เป็นวิธีการแทนคุณสมบัติ

หากผิดพลาดก็เล็กน้อย

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

ยุติธรรม.

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

การประท้วงส่วนใหญ่อาจกล่าวได้สำหรับผู้เริ่มต้นและผู้ตั้งถิ่นฐานของ Java ด้วย - และเราก็ใช้เวลาไม่นานโดยไม่มีปัญหาดังกล่าวในทางปฏิบัติ

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


3
"ปัญหานี้สามารถแก้ไขได้ด้วยการเน้นไวยากรณ์ที่ดีกว่า" : คุณใช้ฟิลด์สาธารณะบ่อยแค่ไหน? _fieldสาขาเอกชนมักจะมีรูปแบบที่แตกต่างกันเช่น fieldหรือเพียงแค่เป็นตัวพิมพ์เล็กแม้
Steven Jeuris

18

ฉันยังไม่ได้อ่านหนังสือและคุณไม่ได้อ้างถึงส่วนที่คุณไม่เข้าใจดังนั้นฉันจะต้องเดา

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

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

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

เป็นข้อโต้แย้งที่คล้ายกันว่าทำไม Linus ถึงเกลียด C ++ รหัสของคุณสามารถสร้างความประหลาดใจให้กับผู้อ่าน เขาเกลียดตัวดำเนินการมากเกินไป: a + bไม่จำเป็นต้องหมายถึงการเติมแบบธรรมดา อาจหมายถึงการดำเนินการที่ซับซ้อนอย่างมากเช่นเดียวกับคุณสมบัติ C # มันอาจมีผลข้างเคียง มันอาจทำอะไรก็ได้

สุจริตฉันคิดว่านี่เป็นข้อโต้แย้งที่อ่อนแอ ทั้งสองภาษาเต็มไปด้วยสิ่งต่างๆเช่นนี้ (เราควรหลีกเลี่ยงตัวดำเนินการมากเกินไปใน C # หรือไม่ท้ายที่สุดสามารถใช้อาร์กิวเมนต์เดียวกันได้ที่นั่น)

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

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

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

เขตข้อมูลสามารถส่งผ่านrefโดยอนุญาตให้ฟังก์ชันที่เรียกว่าเข้าถึงได้โดยตรง สามารถส่งผ่านฟังก์ชันเป็นผู้รับมอบสิทธิ์ได้ทำให้ฟังก์ชันที่เรียกว่าสามารถเข้าถึงได้โดยตรง

สรรพคุณ ... ไม่ได้.

นั่นมันห่วย

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


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

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

ฉันหวังว่าภาษา. net จะให้วิธีการมาตรฐานที่คลาสแสดงคุณสมบัติเป็นrefพารามิเตอร์ สมาชิก (เช่นFoo) ของฟอร์มที่void Foo<T>(ActionByRef<Point,T> proc, ref T param)มีแอตทริบิวต์พิเศษและให้คอมไพเลอร์แปลงthing.Foo.X+=someVar;เป็นFoo((ref Point it, ref int param)=>it.X += param, ref someVar);. เนื่องจากแลมบ์ดาเป็นตัวแทนแบบคงที่จึงไม่จำเป็นต้องมีการปิดและผู้ใช้อ็อบเจ็กต์จะสามารถใช้ตำแหน่งหน่วยเก็บข้อมูลที่สำรองคุณสมบัติเป็นrefพารามิเตอร์แท้(และสามารถส่งต่อไปยังเมธอดอื่นเป็นrefพารามิเตอร์ได้)
supercat

การเขียนแลมบ์ดาด้วยมือทำให้เกิดซอร์สโค้ดที่ดูน่าเบื่อ แต่นั่นเป็นเหตุผลว่าทำไมการให้คอมไพเลอร์ทำการแปลงจะมีประโยชน์ รหัสสำหรับคลาสเพื่อแสดงคุณสมบัติ "callback-by-ref" นั้นค่อนข้างสมเหตุสมผล รหัสที่น่าเกลียดเท่านั้น (ไม่มีการแปลงร่าง) ทางฝั่งผู้โทร
supercat

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

17

ย้อนกลับไปในปี 2009 คำแนะนำนี้ดูเหมือนจะเป็นการอวดอ้างความหลากหลายของWho Moved My Cheese ทุกวันนี้มันล้าสมัยไปเกือบหมดแล้ว

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

ใช่คุณสมบัติสามารถ:

  • ระบุตัวแก้ไขการเข้าถึงที่แตกต่างกันสำหรับ getter และ setter นี่เป็นข้อได้เปรียบเหนือเขตข้อมูล รูปแบบทั่วไปคือการมีผู้เข้าร่วมและผู้ตั้งรับที่ได้รับการป้องกันหรือภายในซึ่งเป็นเทคนิคการสืบทอดที่มีประโยชน์มากซึ่งไม่สามารถทำได้โดยฟิลด์เพียงอย่างเดียว

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

  • ใช้เวลาดำเนินการนาน การเปรียบเทียบที่ถูกต้องที่นี่อยู่กับวิธีการที่ใช้อย่างเท่าเทียมกันยาว - ไม่ฟิลด์ ไม่มีการกำหนดพื้นฐานสำหรับข้อความว่า "วิธีที่เป็นที่ต้องการ" นอกเหนือจากความชอบส่วนตัวของผู้เขียน

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

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

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

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

สิ่งอื่น ๆ ที่คุณสมบัติสามารถทำได้ซึ่งฟิลด์ไม่สามารถรวมไว้ได้:

  • ขี้เกียจโหลดซึ่งเป็นวิธีที่มีประสิทธิภาพที่สุดวิธีหนึ่งในการป้องกันข้อผิดพลาดในการสั่งซื้อเริ่มต้น
  • เปลี่ยนการแจ้งเตือนซึ่งเป็นพื้นฐานทั้งหมดสำหรับMVVMสถาปัตยกรรม
  • การถ่ายทอดทางพันธุกรรมเช่นการกำหนดนามธรรมTypeหรือNameคลาสที่ได้รับมานั้นสามารถให้ข้อมูลเมตาที่น่าสนใจ แต่ยังคงมีข้อมูลเมตาเกี่ยวกับตัวเองอย่างต่อเนื่อง
  • การสกัดกั้นขอบคุณข้างต้น
  • Indexersซึ่งทุกคนที่เคยต้องทำงานกับ COM interop และการItem(i)โทรที่เกิดขึ้นอย่างหลีกเลี่ยงไม่ได้จะรับรู้ว่าเป็นสิ่งที่ยอดเยี่ยม
  • ทำงานกับPropertyDescriptorซึ่งจำเป็นสำหรับการสร้างนักออกแบบและสำหรับกรอบงาน XAML โดยทั่วไป

Richter เป็นนักเขียนที่อุดมสมบูรณ์และรู้ดีเกี่ยวกับ CLR และ C # มาก แต่ฉันต้องบอกว่าดูเหมือนว่าตอนแรกที่เขาเขียนคำแนะนำนี้ (ฉันไม่แน่ใจว่ามันอยู่ในการแก้ไขล่าสุดของเขาหรือไม่ฉันหวังเป็นอย่างยิ่งว่าจะไม่) เขาไม่ต้องการละทิ้งนิสัยเก่า ๆ และมีปัญหาในการยอมรับอนุสัญญาของ C # (เทียบกับ C ++ เป็นต้น)

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

มีรูปแบบการเข้ารหัสที่แข็งแกร่งมากสองแบบใน C # ซึ่งมีการประชุมที่คล้ายกันซึ่งแบ่งปันโดยภาษา CLR อื่น ๆ และ FXCop จะกรีดร้องใส่คุณหากคุณไม่ปฏิบัติตาม:

  1. ทุ่งควรเสมอเป็นส่วนตัวไม่เคยสาธารณะ
  2. ควรประกาศฟิลด์ใน camelCase คุณสมบัติคือ PascalCase

ดังนั้นจึงไม่มีความคลุมเครือว่าFoo.Bar = 42เป็นตัวเข้าถึงคุณสมบัติหรือตัวเข้าถึงฟิลด์ มันเป็นผู้เข้าถึงคุณสมบัติและควรได้รับการปฏิบัติเช่นเดียวกับวิธีการอื่น ๆ - อาจช้าอาจทำให้เกิดข้อยกเว้น ฯลฯ นั่นคือลักษณะของAbstraction - ขึ้นอยู่กับดุลยพินิจของชั้นประกาศว่าจะตอบสนองอย่างไร นักออกแบบชั้นเรียนควรใช้หลักการของความประหลาดใจน้อยที่สุด แต่ผู้โทรไม่ควรคิดอะไรเกี่ยวกับทรัพย์สินยกเว้นว่าจะทำตามที่กล่าวไว้ในกระป๋อง นั่นคือวัตถุประสงค์

ทางเลือกอื่นสำหรับคุณสมบัติคือวิธี getter / setter ทุกที่ นั่นเป็นวิธี Java, และจะได้รับการถกเถียงกันมาตั้งแต่ต้น ถ้านั่นเป็นกระเป๋าของคุณก็ดี แต่มันไม่ใช่แค่วิธีการที่เราม้วนในค่าย. NET เราพยายามอย่างน้อยภายในขอบเขตของระบบแบบคงที่พิมพ์เพื่อหลีกเลี่ยงสิ่งที่ฟาวเลอร์เรียกเสียงรบกวนวากยสัมพันธ์ เราไม่ต้องการเครื่องหมายวงเล็บเสริมget/ setหูดหรือลายเซ็นวิธีพิเศษ - ไม่ใช่หากเราสามารถหลีกเลี่ยงได้โดยไม่สูญเสียความชัดเจน

พูดสิ่งที่คุณต้องการ แต่เสมอไปเป็นจำนวนมากให้อ่านง่ายขึ้นกว่าfoo.Bar.Baz = quux.Answers[42] foo.getBar().setBaz(quux.getAnswers().getItem(42))และเมื่อคุณอ่านวันละหลายพันบรรทัดมันสร้างความแตกต่าง

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


11

ฉันไม่เห็นเหตุผลใด ๆ ที่คุณไม่ควรใช้ Properties โดยทั่วไป

คุณสมบัติอัตโนมัติใน C # 3+ ทำให้ไวยากรณ์ง่ายขึ้นเพียงเล็กน้อย (a la syntatic sugar)


โปรดอ่านหน้า 215-216 ในหนังสือเล่มนั้น ฉันแน่ใจว่าคุณรู้ว่าใครคือเจฟฟรีย์ริกเตอร์!
Quan Mai

ฉันได้อ่าน (CLR ผ่าน C #, 2nd Ed.) ไม่เห็นด้วยกับตำแหน่งของเขาและไม่เห็นเหตุผลที่แท้จริงอย่าใช้คุณสมบัติ!
abatishchev

เป็นหนังสือที่ดี แต่ฉันไม่เห็นด้วยกับผู้เขียนในตอนนี้
Konstantin Tarkus

ที่ไหนกันแน่ที่ผู้เขียนแนะนำว่าอย่าใช้ Properties? ไม่พบสิ่งใดบน p.215-217
Konstantin Tarkus

ฉันเพิ่มความคิดเห็นของเจฟฟรีย์ในคำถามของฉัน :) สามารถพบได้ในหน้า 215-216 ในฉบับพิมพ์ :)
Quan Mai

9

มันเป็นแค่ความคิดเห็นของคน ๆ หนึ่ง ฉันอ่านหนังสือ c # มาแล้วหลายเล่ม แต่ยังไม่เห็นคนอื่นพูดว่า "ไม่ใช้คุณสมบัติ"

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

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

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

เช่น

public class WorkerUsingMethod
{
   // Explicitly obvious that calculation is being done here
   public int CalculateResult()
   { 
      return ExpensiveLongRunningCalculation();
   }
}

public class WorkerUsingProperty
{
   // Not at all obvious.  Looks like it may just be returning a cached result.
   public int Result
   {
       get { return ExpensiveLongRunningCalculation(); }
   }
}

ฉันพบว่าการใช้วิธีการสำหรับกรณีเหล่านี้ช่วยสร้างความแตกต่าง

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

สมมติว่าคุณมีคุณสมบัติดังนี้:

public int Result 
{ 
   get 
   { 
       m_numberQueries++; 
       return m_result; 
   } 
}

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

นี่เป็นข้อแม้เดียวที่ฉันมี ฉันคิดว่าประโยชน์ของคุณสมบัตินั้นมีมากกว่าปัญหา


6

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


+1 สำหรับการเน้นโดยนัยถึงความสำคัญของ "สัญญา" ระหว่างไคลเอนต์และคุณสมบัติของคลาส
TheBlastOne

6

ฉันอดไม่ได้ที่จะเลือกรายละเอียดความคิดเห็นของ Jeffrey Richter:

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

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

วิธีคุณสมบัติอาจทำให้เกิดข้อยกเว้น การเข้าถึงฟิลด์ไม่เคยทำให้เกิดข้อยกเว้น

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


5

ฉันไม่เห็นด้วยกับ Jeffrey Richter แต่ฉันเดาได้ว่าทำไมเขาถึงไม่ชอบคุณสมบัติ (ฉันไม่ได้อ่านหนังสือของเขา)

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

  • ไม่มีการดำเนินการที่ใช้เวลานานเกิดขึ้นภายในคุณสมบัติ getter / setter
  • getter คุณสมบัติไม่มีผลข้างเคียง (เรียกหลายครั้งไม่เปลี่ยนผลลัพธ์)

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


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

4

มีช่วงเวลาหนึ่งที่ฉันคิดว่าจะไม่ใช้คุณสมบัติและนั่นคือการเขียนโค้ด. Net Compact Framework คอมไพลเลอร์ CF JIT ไม่ทำการปรับให้เหมาะสมเช่นเดียวกับคอมไพเลอร์ JIT บนเดสก์ท็อปและไม่ได้เพิ่มประสิทธิภาพตัวเข้าถึงคุณสมบัติแบบง่ายดังนั้นในกรณีนี้การเพิ่มคุณสมบัติแบบง่ายจะทำให้โค้ดขยายตัวเล็กน้อยเมื่อใช้ฟิลด์สาธารณะ โดยปกติแล้วสิ่งนี้จะไม่เป็นปัญหา แต่เกือบตลอดเวลาในโลกของ Compact Framework คุณต้องเผชิญกับข้อ จำกัด ด้านหน่วยความจำที่แน่นหนาดังนั้นการประหยัดเพียงเล็กน้อยเช่นนี้ก็นับได้


4

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

ครั้งหนึ่งฉันเคยเห็นสถานที่ให้บริการที่เรียกว่าลูกค้าที่เปิดการโทรนอกกระบวนการไปยังฐานข้อมูลภายในและอ่านรายชื่อลูกค้า รหัสไคลเอ็นต์มี 'for (int i to customers.Count)' ซึ่งทำให้เกิดการเรียกฐานข้อมูลแยกกันในการทำซ้ำแต่ละครั้งและสำหรับการเข้าถึงของลูกค้าที่เลือก นั่นเป็นตัวอย่างที่เลวร้ายที่แสดงให้เห็นถึงหลักการในการรักษาคุณสมบัติให้เบามาก - แทบจะไม่เกินการเข้าถึงฟิลด์ภายใน

อาร์กิวเมนต์หนึ่งสำหรับการใช้คุณสมบัติคืออนุญาตให้คุณตรวจสอบความถูกต้องของค่าที่ตั้งไว้ อีกประการหนึ่งคือมูลค่าของคุณสมบัติอาจเป็นค่าที่ได้รับไม่ใช่ฟิลด์เดียวเช่น TotalValue = amount * quantity


3

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


3

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

คุณสมบัติไม่เหมือนเขตข้อมูล ฟิลด์มีลักษณะเหมือนคุณสมบัติ

•วิธีคุณสมบัติอาจทำให้เกิดข้อยกเว้น การเข้าถึงฟิลด์ไม่เคยทำให้เกิดข้อยกเว้น

ดังนั้นสนามก็เหมือนกับทรัพย์สินที่รับประกันว่าจะไม่มีข้อยกเว้น

•คุณสมบัติไม่สามารถส่งผ่านเป็นพารามิเตอร์ out หรือ ref ไปยังเมธอดได้ ฟิลด์สามารถ

ดังนั้นเขตข้อมูลก็เหมือนคุณสมบัติ แต่มีความสามารถเพิ่มเติม: ส่งผ่านไปยังวิธีการ a ref/ outยอมรับ

•วิธีการคุณสมบัติอาจใช้เวลานานในการดำเนินการ การเข้าถึงฟิลด์มักจะเสร็จสมบูรณ์ในทันที [... ]

ดังนั้นสนามก็เหมือนคุณสมบัติที่รวดเร็ว

•หากเรียกหลายครั้งติดต่อกันวิธีคุณสมบัติอาจส่งคืนค่าที่แตกต่างกันในแต่ละครั้ง ฟิลด์จะส่งคืนค่าเดียวกันในแต่ละครั้ง คลาส System.DateTime มีคุณสมบัติ readonly Now ที่ส่งคืนวันที่และเวลาปัจจุบัน

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

•วิธีคุณสมบัติอาจทำให้เกิดผลข้างเคียงที่สังเกตได้ การเข้าถึงฟิลด์ไม่เคยทำ

อีกครั้งฟิลด์เป็นคุณสมบัติที่รับประกันว่าจะไม่ทำเช่นนั้น

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

สิ่งนี้อาจจะน่าแปลกใจ แต่ไม่ใช่เพราะมันเป็นคุณสมบัติที่ทำเช่นนี้ แต่แทบจะไม่มีใครส่งคืนสำเนาที่ไม่แน่นอนในคุณสมบัติดังนั้นกรณี 0.1% จึงน่าแปลกใจ


0

วิธีการเรียกใช้แทนคุณสมบัติช่วยลดความสามารถในการอ่านรหัสการเรียกใช้อย่างมาก ตัวอย่างเช่นใน J # การใช้ ADO.NET เป็นฝันร้ายเนื่องจาก Java ไม่รองรับคุณสมบัติและตัวทำดัชนี (ซึ่งโดยพื้นฐานแล้วเป็นคุณสมบัติที่มีอาร์กิวเมนต์) รหัสผลลัพธ์นั้นน่าเกลียดมากโดยมีการเรียกเมธอดวงเล็บว่างไว้ทั่วทุกที่

การสนับสนุนคุณสมบัติและดัชนีเป็นหนึ่งในข้อดีพื้นฐานของ C # บน Java

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