ทรัพย์สินส่วนตัวเทียบกับตัวแปร?


41

เมื่อตั้งค่าให้กับตัวแปรภายในคลาสส่วนใหญ่เราจะแสดงสองตัวเลือก:

private string myValue;
public string MyValue
{
   get { return myValue; }
   set { myValue = value; }
}

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

คำตอบ:


23

ฉันจะนำมันไปอีกขั้นหนึ่งแล้วนำมาให้ 3 ราย แม้ว่าจะมีรูปแบบที่แตกต่างกันในแต่ละครั้งนี้เป็นกฎที่ฉันใช้ส่วนใหญ่เวลาที่การเขียนโปรแกรม C #

ในกรณีที่ 2 และ 3 ให้ไปที่ Property Accessor เสมอ (ไม่ใช่ตัวแปรฟิลด์) และในกรณีที่ 1 คุณจะรอดพ้นจากการที่ต้องเลือกตัวเลือกนี้

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

public class Abc
{ 
  private readonly int foo;

  public Abc(int fooToUse){
    foo = fooToUse;
  }

  public int Foo { get{ return foo; } }
}

2. ) ตัวแปร POCO ตัวแปรอย่างง่ายที่สามารถรับ / ตั้งค่าในขอบเขตสาธารณะ / ส่วนตัวใด ๆ ในกรณีนี้ฉันจะใช้คุณสมบัติอัตโนมัติ

public class Abc
{ 
  public int Foo {get; set;}
}

3. ) คุณสมบัติการรวม ViewModel สำหรับคลาสที่สนับสนุน INotifyPropertyChanged ฉันคิดว่าคุณต้องการตัวแปรฟิลด์ส่วนบุคคลสำรอง

public class Abc : INotifyPropertyChanged
{
  private int foo;

  public int Foo
  {
    get { return foo; }
    set { foo = value;  OnPropertyChanged("foo"); }
  }
}

2
+1 สำหรับตัวอย่าง MVVM จริงๆแล้วนั่นคือสิ่งที่ทำให้เกิดคำถามในตอนแรก
Edward

4
+1: ผสม 2/3 กับ AOP และคุณมีวิธีใช้ INPC ที่ยอดเยี่ยม [แจ้งเตือน] public int Foo {รับ; ตั้ง; }
Steven Evers

1
@Job สำหรับคลาสใด ๆ ที่เข้าถึงคลาสเซ็ตเตอร์ส่วนตัวนั้นเพียงพอสำหรับการเปลี่ยนรูปแบบไม่ได้ อย่างไรก็ตามในชั้นเรียน setter ส่วนตัวไม่ได้ป้องกันการตั้งค่าซ้ำ ๆ หลังจากการสร้างครั้งแรก คุณลักษณะภาษาเช่น 'ชุดแบบอ่านอย่างเดียวโดยส่วนตัว' อาจช่วยแก้ไขปัญหานี้ได้ แต่จะไม่มีอยู่จริง
Sheldon Warkentin

1
ฉันใหม่เพื่อ C # ดังนั้นบอกฉันว่าทำไมใช้public int Foo {get; set;}แทนpublic int Foo?

1
หากคลาสหรือโครงสร้างกำลังทำงานเป็น POCO หรือ PODS ประโยชน์อะไรที่แท้จริงสำหรับการห่อฟิลด์ในคุณสมบัติ ฉันเข้าใจอย่างถ่องแท้ว่าฟิลด์การตัดในคุณสมบัติมีประโยชน์เมื่อความต้องการคลาสหรือโครงสร้างหรืออาจต้องการในอนาคตเพื่อรักษาค่าคงที่ของเนื้อหา (บางทีโดยการทำให้แน่ใจว่าวัตถุอื่น ๆ ได้รับการปรับปรุงให้ตรงกับพวกมัน) แต่ถ้าเป็นคลาสหรือ struct ระบุว่าผู้บริโภคอาจเขียนค่าใด ๆ ในลำดับใด ๆ โดยไม่มีข้อ จำกัด หรือผลข้างเคียงพฤติกรรมที่มีประโยชน์อะไรที่สามารถเพิ่มเข้าไปใน accessor ของสมาชิกได้?
supercat

18

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

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


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

4
@ สัญญาณ: ฉันเดาว่าถ้ามีการตรวจสอบคุณสมบัติ (ตอนนี้หรือในอนาคต) คุณไม่ต้องการเสี่ยงต่อการตรวจสอบความล้มเหลวในระหว่างการก่อสร้าง การตรวจสอบความถูกต้องไม่สมเหตุสมผลในขั้นตอนนี้เนื่องจากไม่สามารถรับประกันวัตถุให้มีเสถียรภาพได้จนกว่าตัวสร้างจะเสร็จสิ้น
Steven Evers

@ สัญญาณ: ทั้งสิ่งที่คุณพูดและสิ่งที่ Snorfus พูด หรือถ้าฉันต้องการบันทึกการเปลี่ยนแปลงของคุณสมบัติฉันอาจไม่ต้องการบันทึกการตั้งค่าเริ่มต้น แต่ฉันพูดว่า "โดยทั่วไป"
pdr

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

4

ขึ้นอยู่กับ

ก่อนอื่นคุณควรชอบคุณสมบัติอัตโนมัติเมื่อทำได้:

public string MyValue {get;set;}

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

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


3
public string MyValue {get; private set;}ฉันยังต้องการ
งาน

3

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

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

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


2

หากมีโอกาสที่การใช้งาน / การตั้งค่าคุณสมบัติเหล่านั้นจะมีการเปลี่ยนแปลงในบางครั้งในภายหลัง (ตัวอย่างเช่นคุณต้องการเพิ่มเหตุการณ์เมื่อมีการโทรsetหรือคุณจะเพิ่มกลไกการประเมินผลแบบขี้เกียจในภายหลังในgetฟังก์ชั่นของคุณ) อาจเป็นความคิดที่ดี รหัสของคุณในชั้นเรียนจะใช้คุณสมบัติในเกือบทุกกรณียกเว้นกรณีที่อาจเกิดขึ้นได้ยากซึ่งคุณไม่ต้องการให้เหตุการณ์หรือกลไกการประเมินผลขี้เกียจใช้อย่างชัดเจน

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


2

ฉันมักจะใช้ทรัพย์สินสาธารณะ

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

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


+1 ได้ในกรณีส่วนใหญ่ (MVVM) เหตุการณ์ PropertyChanged นั้นเป็นสิ่งที่ต้องทำ และนั่นสามารถยิงได้ภายในพื้นที่ คำอธิบายที่ดี
Edward

1

โดยทั่วไปแล้วขึ้นอยู่กับคุณว่าคุณควรทำอย่างไรกับคุณสมบัติและฟิลด์สำรองเมื่อรับ / ตั้งค่า

บ่อยครั้งที่เพียงเพื่อให้สอดคล้องกับรหัสคุณควรใช้ accessors สาธารณะทุกที่ที่มีอยู่และเหมาะสม ที่ช่วยให้คุณ refactor ด้วยการเปลี่ยนรหัสน้อยที่สุด; หากวิธีการตั้งค่านี้ต้องแยกออกจากชั้นเรียนและวางที่อื่นที่ไม่มีเขตข้อมูลสำรองอีกต่อไป (เช่นคลาสพื้นฐาน) ใครจะสนใจ? คุณกำลังใช้สิ่งที่มีอยู่ไม่ว่าที่ใดในชั้นเรียนจะทำงาน เขตข้อมูลสำรองในกรณีส่วนใหญ่จะมีรายละเอียดการใช้งาน ไม่มีใครนอกห้องเรียนของคุณที่ควรรู้ว่ามีอยู่จริง

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


0

ใช้อันที่เหมาะสมเสมอ ใช่ฉันรู้ว่ามันฟังดูผิดเพี้ยนไปจนไม่ใช่คำตอบ

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

public Foo Bar
{
  get { return _bar; }
  set { _bar = doSomethingTo(value); }
}

แต่ในสถานการณ์อื่นคุณอาจใช้พร็อพเพอร์ตี้เป็นมุมมองของโมเดลข้อมูล:

public Double SomeAngleDegrees
{
  get { return SomeAngleRadians * 180 / PI; }
  set { SomeAngleRadians = value * PI / 180; }
}

หากมันสมเหตุสมผลที่จะใช้รูปแบบเรเดียนจากSomeAngleนั้นโดยทั้งหมดใช้มัน

ในท้ายที่สุดอย่าลืมดื่มเครื่องช่วยคูลของคุณเอง API สาธารณะที่คุณพบควรมีความยืดหยุ่นเพียงพอที่จะทำงานภายในได้

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