ตัวแปรที่ประกาศใน for-loop เป็นตัวแปรภายในหรือไม่


133

ฉันใช้ C # มาเป็นเวลานาน แต่ไม่เคยตระหนักถึงสิ่งต่อไปนี้:

 public static void Main()
 {
     for (int i = 0; i < 5; i++)
     {

     }

     int i = 4;  //cannot declare as 'i' is declared in child scope                
     int A = i;  //cannot assign as 'i' does not exist in this context
 }

เหตุใดฉันจึงไม่สามารถใช้ค่า 'i' นอกบล็อก for ได้หากไม่อนุญาตให้ฉันประกาศตัวแปรด้วยชื่อนี้

ฉันคิดว่าตัวแปรตัววนซ้ำที่ใช้โดย for-loop นั้นใช้ได้เฉพาะในขอบเขตเท่านั้น


8
เนื่องจากขอบเขตบล็อกด้านนอกมีขอบเขตของ for loop
V4Vendetta

3
วิธีหนึ่งที่ฉันคิดถึงมัน (และคำแนะนำสไตล์โค้ดบางอย่างต้องการสิ่งนี้โดยเฉพาะสำหรับภาษาที่พิมพ์แบบไดนามิก) คือตัวแปรทั้งหมดที่ประกาศในขอบเขตสามารถประกาศได้เมื่อเริ่มต้นขอบเขตนั้นซึ่งหมายความว่าฟังก์ชันของคุณสามารถเขียนใหม่ได้int i, A; for(int i = 0; i < 5; i++){ } i=4; A=i
Keith

2
@ V4Vendetta: มันเป็นอีกทางหนึ่ง บล็อกด้านในเป็นกล่องดำไปยังบล็อกหลัก
Sebastian Mach

4
นอกเหนือจากเหตุผลทางเทคนิคว่าทำไมจึงไม่สามารถทำได้เหตุใดสิ่งนี้ (หรือตัวแปรนี้) จึงเป็นสิ่งที่สมเหตุสมผลที่จะทำ! เห็นได้ชัดว่ามีวัตถุประสงค์ที่แตกต่างกันดังนั้นตั้งชื่ออื่น
Dave

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

คำตอบ:


120

เหตุผลที่คุณไม่ได้รับอนุญาตให้กำหนดตัวแปรที่มีชื่อเดียวกันทั้งใน for-loop และภายนอก for-loop เนื่องจากตัวแปรในขอบเขตภายนอกนั้นใช้ได้ในขอบเขตภายใน หมายความว่าจะมีตัวแปร 'i' สองตัวอยู่ใน for-loop หากได้รับอนุญาต

ดู: ขอบเขต MSDN

โดยเฉพาะ:

ขอบเขตของตัวแปรโลคัลที่ประกาศในการประกาศตัวแปรโลคัล (ส่วนที่ 8.5.1) คือบล็อกที่การประกาศเกิดขึ้น

และ

ขอบเขตของตัวแปรโลคัลที่ประกาศใน for-initializer ของคำสั่ง for (ส่วน 8.8.3) คือ for-initializer, for-condition, for-iterator และคำสั่งที่มีอยู่ของคำสั่ง for

และ: การประกาศตัวแปรท้องถิ่น (ส่วนที่ 8.5.1 ของข้อกำหนด C #)

โดยเฉพาะ:

ขอบเขตของตัวแปรโลคัลที่ประกาศในการประกาศตัวแปรภายในคือบล็อกที่การประกาศเกิดขึ้น เป็นข้อผิดพลาดในการอ้างถึงตัวแปรโลคัลในตำแหน่งข้อความที่นำหน้า local-variable-declarator ของตัวแปรโลคัล ภายในขอบเขตของตัวแปรโลคัลเป็นข้อผิดพลาดเวลาคอมไพล์เพื่อประกาศตัวแปรโลคัลหรือค่าคงที่อื่นที่มีชื่อเดียวกัน

(เน้นของฉัน)

ซึ่งหมายความว่าขอบเขตของiภายใน for-loop ของคุณคือ for-loop ในขณะที่ขอบเขตของiด้านนอกของ for-loop ของคุณคือวิธีการหลักทั้งหมดบวกกับ for-loop หมายความว่าคุณมีเหตุการณ์ที่เกิดขึ้นสองครั้งiในลูปซึ่งไม่ถูกต้องตามที่กล่าวไว้ข้างต้น

สาเหตุที่คุณไม่ได้รับอนุญาตให้ทำint A = i;เนื่องจากint iมีการกำหนดขอบเขตให้ใช้ภายในforลูปเท่านั้น ดังนั้นจึงไม่สามารถเข้าถึงได้จากภายนอกforลูปอีกต่อไป

ดังที่คุณเห็นปัญหาทั้งสองนี้เป็นผลมาจากการกำหนดขอบเขต ปัญหาแรก ( int i = 4;) จะส่งผลให้เกิดiตัวแปรสองตัวภายในforขอบเขตการวนซ้ำ ในขณะที่int A = i;จะส่งผลให้เข้าถึงตัวแปรที่อยู่นอกขอบเขต

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

public static void Main()
{
    int i;

    for (i = 0; i < 5; i++)
    {

    }

    // 'i' is only declared in the method scope now, 
    // no longer in the child scope -> valid.
    i = 4;

    // 'i' is declared in the method's scope -> valid. 
    int A = i;
}

แก้ไข :

แน่นอนว่าคอมไพเลอร์ C # สามารถเปลี่ยนแปลงได้เพื่อให้โค้ดนี้คอมไพล์ถูกต้อง หลังจากทั้งหมดนี้ถูกต้อง:

for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i);
}

for (int i = 5; i > 0; i--)
{
    Console.WriteLine(i);
}

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

public static void Main()
{
    int i = 4;

    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(i);
    }

    for (int i = 5; i > 0; i--)
    {
        Console.WriteLine(i);
    }

    Console.WriteLine(i);
}

ลองคิดดูว่าอาจเกิดข้อผิดพลาดได้ที่นี่ครั้งล่าสุดiพิมพ์ 0 หรือ 4 หรือไม่? ตอนนี้เป็นตัวอย่างเล็ก ๆ ซึ่งค่อนข้างง่ายต่อการติดตาม แต่แน่นอนว่าสามารถบำรุงรักษาและอ่านได้น้อยกว่าการประกาศด้านนอกiด้วยชื่ออื่น

หมายเหตุ:

โปรดทราบ, C # 's กฎกำหนดขอบเขตแตกต่างจากC ++ กฎระเบียบกำหนดขอบเขต ในตัวแปร C ++ จะอยู่ในขอบเขตจากตำแหน่งที่ประกาศไปจนถึงจุดสิ้นสุดของบล็อกเท่านั้น ซึ่งจะทำให้โค้ดของคุณเป็นโครงสร้างที่ถูกต้องใน C ++


สมเหตุสมผลฉันคิดว่ามันควรจะผิดพลาดกับiตัวแปรภายใน ซึ่งดูเหมือนชัดเจนมากขึ้นสำหรับฉัน
George Duckett

แต่ขอบเขตมีไว้สำหรับคำสั่งและ {}? หรือหมายถึงแม่ของมัน {}?
John V

2
ถ้าฉันประกาศ 'A' หลังจากคำสั่ง for มันจะไม่ถูกต้องใน for loop เนื่องจากมีการประกาศในภายหลัง นั่นคือเหตุผลที่ฉันไม่เข้าใจว่าทำไมไม่สามารถใช้ชื่อเดียวกันได้
John V

9
บางทีคำตอบนี้ควรชี้ให้เห็นว่ากฎขอบเขต C # แตกต่างจากกฎสำหรับ C ++ ในกรณีนี้เพื่อความสมบูรณ์ ใน C ++ ตัวแปรจะอยู่ในขอบเขตจากที่ประกาศไปจนถึงจุดสิ้นสุดของบล็อกเท่านั้น (ดูmsdn.microsoft.com/en-us/library/b7kfh662(v=vs.80).aspx )
AAT

2
โปรดทราบว่า Java ใช้แนวทางระดับกลางระหว่าง C ++ และ C #: เนื่องจากเป็นตัวอย่างของ OP จึงใช้ได้ใน Java แต่ถ้าiคำจำกัดความภายนอกถูกย้ายไปก่อนสำหรับลูปiคำจำกัดความภายในจะถูกทำเครื่องหมายว่าไม่ถูกต้อง
Nicola Musatti

29

คำตอบของ J.Kommer ถูกต้อง: สั้น ๆ มันผิดกฎหมายสำหรับการประกาศตัวแปรโลคัลในพื้นที่ประกาศตัวแปรโลคัลที่ทับซ้อนพื้นที่การประกาศตัวแปรโลคัลอื่นที่มีโลคัลชื่อเดียวกัน

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

class C
{
    int x;
    void M()
    {
        int y = x;
        if(whatever)
        {
            int x = 123;

เนื่องจากตอนนี้ชื่อธรรมดา "x" ถูกใช้ภายในพื้นที่ประกาศตัวแปรโลคัลของ "y" เพื่อหมายถึงสองสิ่งที่แตกต่างกัน - "this.x" และ "x" แบบโลคัล

ดูhttp://blogs.msdn.com/b/ericlippert/archive/tags/simple+names/สำหรับการวิเคราะห์เพิ่มเติมเกี่ยวกับปัญหาเหล่านี้


2
นอกจากนี้โปรดทราบว่าโค้ดตัวอย่างของคุณจะคอมไพล์เมื่อคุณทำการเปลี่ยนแปลงint y = this.x;
Phil

4
@ ฟิล: ถูกต้อง. this.xไม่ได้เป็นชื่อที่เรียบง่าย
Eric Lippert

13

มีวิธีการประกาศและใช้iภายในวิธีการหลังลูป:

static void Main()
{
    for (int i = 0; i < 5; i++)
    {

    }

    {
        int i = 4;
        int A = i;
    }
}

คุณสามารถทำได้ใน Java (อาจมาจาก C ฉันไม่แน่ใจ) แน่นอนว่ามันค่อนข้างจะยุ่งเพราะชื่อตัวแปร


ใช่สิ่งนี้มาจาก C / C ++
Branko Dimitrijevic

1
ฉันไม่รู้เรื่องนี้มาก่อน คุณสมบัติภาษาเรียบร้อย!

@MathiasLykkegaardLorenzen ใช่
Chris S

7

หากคุณต้องการประกาศi ก่อนที่คุณforห่วงคุณคิดว่ามันควรจะยังคงถูกต้องในการประกาศไว้ในวง?

ไม่เพราะขอบเขตของทั้งสองจะทับซ้อนกัน

สำหรับการที่ไม่สามารถทำได้int A=i;นั่นเป็นเพียงเพราะiมีอยู่ในforวงเท่านั้นเหมือนที่ควรทำ


7

นอกเหนือจากคำตอบของ J. Kommer (+1 btw) มีสิ่งนี้อยู่ในมาตรฐานสำหรับขอบเขต NET:

block หากคุณประกาศตัวแปรภายในโครงสร้างบล็อกเช่นคำสั่ง If ขอบเขตของตัวแปรนั้นจะอยู่จนถึงจุดสิ้นสุดของบล็อกเท่านั้น อายุการใช้งานอยู่จนกว่าขั้นตอนจะสิ้นสุด

ขั้นตอน หากคุณประกาศตัวแปรภายในโพรซีเดอร์ แต่อยู่นอกคำสั่ง If ใด ๆ ขอบเขตจะอยู่จนถึง End Sub หรือ End Function อายุการใช้งานของตัวแปรคือจนกว่าโพรซีเดอร์จะสิ้นสุด

ดังนั้น int i decalared ภายในส่วนหัว for loop จะอยู่ในขอบเขตเฉพาะในช่วง for loop block แต่อายุการใช้งานจะคงอยู่จนกว่าMain()โค้ดจะเสร็จสมบูรณ์


5

วิธีที่ง่ายที่สุดในการคิดเกี่ยวกับเรื่องนี้คือย้ายการประกาศภายนอกของ I ไปไว้เหนือลูป มันควรจะชัดเจนแล้ว

เป็นขอบเขตเดียวกันทั้งสองวิธีจึงไม่สามารถทำได้


4

นอกจากนี้กฎของ C # ยังมีหลายครั้งที่ไม่จำเป็นในแง่ของการเขียนโปรแกรมอย่างเคร่งครัด แต่มีไว้เพื่อให้โค้ดของคุณสะอาดและอ่านได้

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


2

คำตอบของ Kommer นั้นถูกต้องในทางเทคนิค ขอฉันถอดความด้วยอุปมาแบบจอตาบอดที่สดใส

มีหน้าจอตาบอดทางเดียวระหว่างสำหรับบล็อกและบล็อกด้านนอกที่ล้อมรอบเพื่อให้รหัสจากภายในบล็อกสามารถมองเห็นรหัสภายนอกได้ แต่รหัสในบล็อกด้านนอกไม่สามารถมองเห็นรหัสภายในได้

เนื่องจากโค้ดด้านนอกไม่สามารถมองเห็นด้านในจึงไม่สามารถใช้สิ่งที่ระบุไว้ภายในได้ แต่เนื่องจากโค้ดใน for-block สามารถมองเห็นได้ทั้งภายในและภายนอกจึงไม่สามารถใช้ตัวแปรที่ประกาศในทั้งสองแห่งโดยใช้ชื่อได้อย่างชัดเจน

ดังนั้นคุณไม่เห็นมันหรือคุณ C #!


0

ดูในลักษณะเดียวกับที่คุณสามารถประกาศintในusingบล็อก:

using (int i = 0) {
  // i is in scope here
}
// here, i is out of scope

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

อีกวิธีหนึ่งคือการพูดว่า

if (true) {
  int i = 0;
  // i is in scope here
}
// here, i is out of scope

หวังว่านี่จะช่วยให้เห็นภาพสิ่งที่เกิดขึ้น

ฉันชอบฟีเจอร์นี้มากเพราะการประกาศintจากforวงในทำให้โค้ดดีและแน่น


WTF? หากคุณจะแท็ก NEG ให้ลูกพูดว่าทำไม
jp2code

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