มีเหตุผลใดที่จะใช้คุณสมบัติส่วนตัวใน C # หรือไม่


246

ฉันเพิ่งรู้ว่าการสร้างคุณสมบัติ C # สามารถใช้กับโมดิฟายเออร์ส่วนตัวได้ :

private string Password { get; set; }

แม้ว่านี่จะเป็นที่น่าสนใจในทางเทคนิค, ฉันไม่สามารถจินตนาการเมื่อผมจะใช้มันตั้งแต่ข้อมูลส่วนตัวที่เกี่ยวข้องกับการแม้กระทั่งพิธีน้อย :

private string _password;

และฉันไม่สามารถจินตนาการได้ว่าเมื่อใดที่ฉันจะต้องได้รับภายในแต่ไม่ได้ตั้งค่าหรือตั้งค่าแต่ไม่ได้รับข้อมูลส่วนตัว:

private string Password { get; }

หรือ

private string Password { set; }

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

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

ภาคผนวก

คำตอบที่ดีอ่านผ่านพวกเขาฉันคัดการใช้งานเหล่านี้สำหรับคุณสมบัติส่วนตัว:

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

เทคนิคที่ได้รับการสนับสนุนโดยคุณสมบัติส่วนตัวคือการห่อหุ้มตัวเอง - ดูที่: sourcemaking.com/refactoring/self-encapsulate-field
LBushkin

คำตอบ:


212

ฉันใช้มันหากฉันต้องการแคชค่าและต้องการขี้เกียจโหลด

private string _password;
private string Password
{
    get
    {
        if (_password == null)
        {
            _password = CallExpensiveOperation();
        }

        return _password;
    }
}

42
รูปแบบทั่วไปที่ดีสำหรับสิ่งนี้คือreturn _password ?? (_password = CallExpensiveOperation());
Marc

1
@EvAlex Lazy อาจจะดีกว่าถ้าคุณสามารถใช้มันได้ แต่สามารถใช้สิ่งคงที่เท่านั้นดังนั้นคุณจึงไม่สามารถเข้าถึง (ไม่คงที่) วิธีการหรือสมาชิกคนอื่น ๆ โดยวิธีที่ฉันชอบไวยากรณ์บรรทัดเดียวreturn _password ?? (_password = CallExpensiveOperation());ตั้งแต่ในขณะ หรือ Resharper ชอบมัน :)
บาร์ต

4
@ Marc ตั้งแต่ C # 6 คุณไม่ต้องเขียนและรับ private string Password => _password ?? (_password = CallExpensiveOperation());
Otto Abnormalverbraucher

1
ด้วย C # 8 มันยิ่งสั้นลง:private string Password => _password ??= CallExpensiveOperation();
Dave M

2
@Bas นั่นไม่ใช่การโหลดขี้เกียจเพราะCallExpensiveOperation();ถูกเรียกระหว่างการก่อสร้าง / การเริ่มต้นของวัตถุที่มีและไม่ใช่เมื่อมีการเข้าถึงคุณสมบัติเป็นครั้งแรก
Stefan Podskubka

142

การใช้งานหลักของสิ่งนี้ในรหัสของฉันคือการเริ่มต้นขี้เกียจตามที่คนอื่น ๆ ได้กล่าวถึง

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

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


5
"โค้ดมีประสิทธิภาพมากกว่าข้อมูล" หรือเปล่า? Googling ส่งคืนการอ้างอิงที่ชี้ไปที่คุณ แค่อยากรู้เพื่อที่ฉันจะได้พูดถูกต้องเมื่อฉันต้องการ
Joan Venge

24
@Joan: ฉันไม่รู้ ไม่ว่าฉันจะสร้างมันขึ้นมาหรือฉันได้ยินคนอื่นพูดแล้วคิดว่า "ว้าวฉันควรขโมยทั้งหมดแล้วลืมไปเลยว่าฉันขโมยมันมาจากไหน"
Eric Lippert

1
+ CodeLens บอกให้คุณทราบถึงสถานที่ที่มีการอ้างอิง
UuDdLrLrSs

43

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

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

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


ในกรณีที่เกี่ยวข้องกับกึ่ง (แม้ว่าจะแตกต่างจากคำถามของคุณ) ฉันมักใช้ setters ส่วนตัวกับทรัพย์สินสาธารณะ:

public string Password 
{
    get; 
    private set;
}

สิ่งนี้จะทำให้คุณทะเยอทะยานในที่สาธารณะ แต่ให้สุนัขเซทเทอร์เป็นส่วนตัว


+1 เหมาะสม: "ถ้าฉันรู้สึกว่าคุณสมบัติอาจต้องใช้ตรรกะเพิ่มเติมในบางครั้งฉันจะห่อมันไว้ในทรัพย์สินส่วนตัวแทนที่จะใช้ฟิลด์ดังนั้นฉันจึงไม่ต้องเปลี่ยนรหัสในภายหลัง"
Edward Tanguay

7
setters ส่วนตัว <3
Earlz

20

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


20

การกำหนดค่าเริ่มต้นของ Lazy เป็นหนึ่งในที่ที่สามารถจัดระเบียบได้เช่น

private Lazy<MyType> mytype = new Lazy<MyType>(/* expensive factory function */);

private MyType MyType { get { return this.mytype.Value; } }

// In C#6, you replace the last line with: private MyType MyType => myType.Value;

จากนั้นคุณสามารถเขียน: this.MyTypeทุกหนทุกแห่งมากกว่าthis.mytype.Valueและแค็ปซูลความจริงที่ว่ามันเป็นอินสแตนซ์ที่ขี้เกียจในที่เดียว

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


2
ตกลงว่ามันจะมีขอบเขตที่นั่น
Chris Marisic

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

5
@Eric Lippert - field-declarationขอบเขตภายในaccessor-declarationsมีอันดับ 1 ในรายการสินค้าที่ต้องการ C # เป็นเวลานาน หากคุณได้รับการออกแบบและนำไปใช้ในเวอร์ชันอนาคต (จริง) บางอย่างฉันจะอบเค้กให้คุณ
Jeffrey L Whitledge

13

การใช้เพียงครั้งเดียวที่ฉันคิดได้

private bool IsPasswordSet 
{ 
     get
     {
       return !String.IsNullOrEmpty(_password);
     }
}

+1 สำหรับคลาสที่มีประโยชน์ของคุณสมบัติที่คำนวณจากตัวแปรส่วนตัวอื่น ๆ
Daren Thomas

2
ทำไมไม่ใช้วิธีการแบบส่วนตัวprivate bool IsPasswordSet() { return !String.IsNullOrEmpty(_password); }
Roman

10

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

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


1: "คุณยังสามารถนำเสนอสัญญาให้กับตัวเองด้วยเหตุผลที่คล้ายกันมาก" ทำให้รู้สึก
เอ็ดเวิร์ด Tanguay

8

ฉันใช้มันในการทำให้เป็นอนุกรมกับสิ่งที่ชอบDataContractSerializerหรือ protobuf-net ที่รองรับการใช้งานนี้ ( XmlSerializerไม่) มันมีประโยชน์ถ้าคุณต้องการทำให้วัตถุเป็นส่วนหนึ่งของการทำให้เป็นอนุกรม:

public SomeComplexType SomeProp { get;set;}
[DataMember(Order=1)]
private int SomePropProxy {
    get { return SomeProp.ToInt32(); }
    set { SomeProp = SomeComplexType.FromInt32(value); }
}

6

สิ่งหนึ่งที่ฉันทำตลอดเวลาคือเก็บตัวแปร / แคช "ระดับโลก" ไว้ HttpContext.Current

private static string SomeValue{
  get{
    if(HttpContext.Current.Items["MyClass:SomeValue"]==null){
      HttpContext.Current.Items["MyClass:SomeValue"]="";
    }
    return HttpContext.Current.Items["MyClass:SomeValue"];
  }
  set{
    HttpContext.Current.Items["MyClass:SomeValue"]=value;
  }
}

5

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

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


Ditto; หากมีเหตุผลเกี่ยวข้องในการรับ / การตั้งค่าบางครั้งฉันอาจใช้ทรัพย์สินส่วนตัวหรือได้รับการคุ้มครอง โดยทั่วไปแล้วจะขึ้นอยู่กับจำนวนตรรกะ: ตรรกะง่ายๆที่ฉันจะทำในคุณสมบัติตรรกะจำนวนมากที่ฉันมักจะใช้ฟังก์ชั่นเสริม สิ่งที่ทำให้รหัสบำรุงรักษามากที่สุด
TechNeilogy

5

ฉันใช้คุณสมบัติส่วนตัวเพื่อลดรหัสสำหรับการเข้าถึงคุณสมบัติย่อยซึ่งมักจะใช้

    private double MonitorResolution
    {
        get { return this.Computer.Accesories.Monitor.Settings.Resolution; }
    }

มันจะมีประโยชน์หากมีคุณสมบัติย่อยมากมาย


2

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


2

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

ถ้ามันเป็นแค่สนามรองรับตรง ไม่มีสิ่งใดในใจที่เป็นเหตุผลที่ดี


2

ฉันรู้ว่าคำถามนี้เก่ามาก แต่ข้อมูลด้านล่างไม่ได้อยู่ในคำตอบปัจจุบัน

ฉันไม่สามารถจินตนาการได้ว่าเมื่อใดที่ฉันจะต้องได้รับภายใน แต่ไม่ได้ตั้งค่าไว้

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

นอกจากนี้ Visual Studio Professional จะให้ข้อมูลเกี่ยวกับคุณสมบัติและไม่ใช่ฟิลด์ที่ทำให้ง่ายต่อการดูว่าคุณใช้ฟิลด์ใด

PorpField


1

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

  • การตรวจสอบ

    string _password;
    string Password
    {
        get { return _password; }
        set
        {
            // Validation logic.
            if (value.Length < 8)
            {
                throw new Exception("Password too short!");
            }
    
            _password = value;
        }
    }
  • ล็อค

    object _lock = new object();
    object _lockedReference;
    object LockedReference
    { 
        get
        {
            lock (_lock)
            {
                return _lockedReference;
            }
        }
        set
        {
            lock (_lock)
            {
                _lockedReference = value;
            }
        }
    }

    หมายเหตุ: เมื่อล็อคการอ้างอิงคุณจะไม่ล็อคการเข้าถึงสมาชิกของวัตถุที่อ้างอิง

อ้างอิง Lazy: เมื่อโหลดขี้เกียจคุณอาจท้ายต้องที่จะทำมัน async ซึ่งในปัจจุบันมีAsyncLazy หากคุณใช้เวอร์ชั่นเก่ากว่า Visual Studio SDK 2015 หรือไม่ใช้คุณสามารถใช้AsyncLazy ของ AsyncExได้


0

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

  • คุณจำเป็นต้องใช้refหรือoutมีค่า - อาจเป็นเพราะตัวInterlockedนับ
  • มันมีจุดมุ่งหมายเพื่อแสดงเค้าโครงพื้นฐานตัวอย่างเช่นบนstructด้วยเค้าโครงชัดเจน (อาจแมปกับ C ++ การถ่ายโอนข้อมูลหรือunsafeรหัส)
  • ในอดีตประเภทที่ใช้กับBinaryFormatterการจัดการฟิลด์โดยอัตโนมัติ (เปลี่ยนเป็นอุปกรณ์ประกอบฉากอัตโนมัติเปลี่ยนชื่อและทำให้ตัวแบ่งอนุกรม)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.