ทำความเข้าใจกับคำหลักแบบคงที่


16

ฉันมีประสบการณ์ในการพัฒนากับ Java, Javascript และ PHP

ฉันกำลังอ่าน Microsoft Visual C # 2010 ทีละขั้นตอนซึ่งฉันรู้สึกว่ามันเป็นหนังสือที่ดีมากในการแนะนำคุณกับภาษา C #

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

แต่วัตถุประสงค์ที่แท้จริงของคำหลักคงที่คืออะไร เมื่อใดที่ฉันควรประกาศตัวแปรคงที่และวิธีการ?


4
static ใน C # ใกล้เคียงกับ static ใน Java หากคุณเข้าใจใน Java คุณจะต้องไม่มีปัญหาใด ๆ ใน C #
superM

Java เป็นภาษาการเขียนโปรแกรมแรกของฉันและฉันไม่เข้าใจแนวคิดนี้ที่นั่นฉันใช้ Java ในช่วงเวลาสั้น ๆ เท่านั้น
Nistor Alexandru

กล่าวโดยย่อ: ใช้ "คงที่" เมื่อคุณไม่ต้องการการวางแนววัตถุตัวอย่างเช่นเพียงบางวิธีหรือตัวแปรแบบสแตนด์อโลน การประกาศคลาสให้เป็นแบบสแตติกหมายถึงการใส่ฟังก์ชั่นที่ไม่ใช่วัตถุและตัวแปรในชื่อสามัญ (ช่องว่าง) ชื่อคลาส
Doc Brown

คำตอบ:


15

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

public class SomeObject
{
    //Static Field
    static int Foo = 3;

    //instance field
    private int _Foo2 = 4;

    //instance property
    public int Foo2{get{return _Foo2;}set{_Foo2 = value;}}


    //static factory method
    public static SomeObject CreateSomeObject(int fooValue)
    {
        SomeObject retVal = new SomeObject();
        retVal.Foo2 = fooValue;
        return retVal;
    }

    //Parameterless instance constructor
    public SomeObject()
    {
    }

    public static int Add(int x)
    {
        //Static methods can only deal with local variables, or fields that
        //  are also static in the class.  This one adds x to the static member foo
        return x + Foo;

        //Foo2 is not accessable here!
    }

      //Instance method
    public int AddSomething(int x)
    {
        //Add x to the property value of Foo2
        return x + this.Foo2;

        //Note that Foo *is* accessable here as 'SomeObject.Foo'
    }

}

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

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

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

public static void Main(string[] args)
{
    //This is a static method that starts a thread in an application
    // space.  At this point not everything actually has to be static...

    //Here is an instantiation with a parameterless contruction
    SomeObject obj = new SomeObject();

    //Here is an instantiation using a static factory method
    SomeObject obj2 = SomeObject.CreateSomeObject(3);

    //Getting field value from static field
    // Notice that this references the class name, not an instance
    int fooValue1 = SomeObject.Foo;

    //Getting property value from instance
    //  Note that this references an object instance
    int fooValue2 = obj2.Foo2;

    //Instance method must be called through an object
    obj2.AddSomething(4);  //if default constructor, would return 8

    //Static methods must be called through class name
    SomeObject.Add(4); //Returns 7
}

นอกจากนี้ตรวจสอบโพสต์นี้เพื่อดูลึกลงไปในชั้นเรียนแบบคงที่


18

นี่คือวิธีอธิบายของ Joshua Bloch ซึ่งฉันคิดว่ามันยอดเยี่ยมเหมือนที่เขาพูด (ใช่ฉันเป็นแฟนของ Joshua Bloch :)) นี่คือการอ้างอิงจากหน่วยความจำ

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

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

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


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

12

การมองด้วยวิธีนี้ช่วยฉันได้:

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

เป็นเวลาที่จะใช้คำหลักคงที่:

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

+1 สำหรับการสรุปที่ดีฉันไม่ทราบว่าทุกประเภทมี 1 อินสแตนซ์คงที่และฉันคิดว่ามันแปลกที่จะบอกความจริงกับคุณ
NoChance

2
@EmmadKareem นั่นเป็นเพียงรูปแบบการใช้แบบ pdr ทางจิตเพราะ"Looking at it this way helps"เขา คุณคิดว่ามันแปลกเพราะมันไม่จริง แต่คุณสามารถคิดแบบนี้ถ้าคุณต้องการ คุณรู้จักรุ่น Bohr หรือไม่? มันเป็นชุดของกฎและแนวคิดที่อะตอมและอิเลคตรอนโต้ตอบซึ่งกันและกัน รูปแบบการทำงานขึ้นอยู่กับสิ่งที่คุณทำ แต่มันไม่ได้เป็นความจริง
phant0m

@ phant0m ขอบคุณสำหรับคำอธิบายฉันอยู่ภายใต้ความรู้สึกว่ามันไม่ใช่ของจริงและฉันรู้สึกประหลาดใจเพราะสิ่งนั้น
NoChance

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

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

4

คำอธิบายที่ง่ายที่สุด --- คงที่ => จะมีเพียงหนึ่งสำเนาต่อหนึ่งสภาพแวดล้อม

ดังนั้นภายใน VM หรือ CLR จะมีเพียงหนึ่งสำเนาของคลาสแบบสแตติกและคลาสอื่น ๆ ที่อ้างอิงจะต้องแชร์วิธีการและข้อมูลกับคลาสอื่น ๆ ทั้งหมดที่อ้างถึง

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


2

สมาชิกแบบสแตติกจะเชื่อมโยงกับคลาสไม่ใช่กับอินสแตนซ์ของคลาสนั้น

เนื่องจากเรากำลังพูดถึง .Net พิจารณาStringระดับโดยเฉพาะในสปลิตและเข้าร่วมกับวิธีการ

แยกเป็นวิธีการอินสแตนซ์ สร้างตัวแปร String ให้ค่าและคุณสามารถเรียกใช้ Split () บนตัวแปร / ค่านั้นและกลับอาร์เรย์ของ "bits":

String s1 = "abc,def,ghi" ; 
String[] array2 = s1.Split( ',' ) ; 

ดังนั้นสำหรับวิธีการเช่นค่าจัดขึ้นภายในเช่นชั้นที่ได้รับเรื่อง

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

String[] array3 = { ... } 
s1 = String.Join( array3, "," ) ; 

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

// Maybe one day ... 
String s4 = "," ; 
s1 = s4.Join( array3 ) ; 

2

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

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

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

"สมาชิกรหัส" ใด ๆ (ฟังก์ชั่น, คุณสมบัติ, เขตข้อมูล) ประกาศว่าstaticเข้ามาในขอบเขตของบรรทัดแรกของmain()ฟังก์ชั่นของโปรแกรมและไม่ปล่อยให้มันจนกว่าmain()ฟังก์ชั่นจะสิ้นสุดลง ในภาษาอังกฤษล้วนมีสมาชิกแบบสแตติกอยู่และสามารถใช้งานได้ตราบใดที่โปรแกรมกำลังทำงาน นอกจากนี้สมาชิกแบบสแตติกจะถูกเรียกใช้โดยเรียกพวกเขาเป็นสมาชิกของชนิดเองไม่ใช่สมาชิกของอินสแตนซ์ประเภทใดประเภทหนึ่ง:

public class Foo
{
   public int MyInt {get;set;} //this is an "instance member"
   public static int MyStaticInt {get;set;} //this is a "static member"
}

...

var myFoo = new Foo();
myFoo.MyInt = 5; //valid
myFoo.MyStaticInt = 5; //invalid; MyStaticInt doesn't belong to any one Foo

Foo.MyInt = 5; //invalid; MyInt only has meaning in the context of an instance
Foo.MyStaticInt = 2; //valid

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

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

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

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