ถ้าฉันสร้างบูลในชั้นเรียนของฉันbool check
มันก็เหมือนกับค่าเริ่มต้นที่เป็นเท็จ
เมื่อฉันสร้างบูลเดียวกันภายในเมธอดของฉันbool check
(แทนที่จะเป็นคลาส) ฉันได้รับข้อผิดพลาด "ใช้การตรวจสอบตัวแปรโลคอลที่ไม่ได้กำหนด" ทำไม?
ถ้าฉันสร้างบูลในชั้นเรียนของฉันbool check
มันก็เหมือนกับค่าเริ่มต้นที่เป็นเท็จ
เมื่อฉันสร้างบูลเดียวกันภายในเมธอดของฉันbool check
(แทนที่จะเป็นคลาส) ฉันได้รับข้อผิดพลาด "ใช้การตรวจสอบตัวแปรโลคอลที่ไม่ได้กำหนด" ทำไม?
คำตอบ:
Yuval และคำตอบของดาวิดนั้นถูกต้องแล้ว สรุป:
ผู้วิพากษ์วิจารณ์คำตอบของเดวิดถามว่าทำไมมันเป็นไปไม่ได้ที่จะตรวจจับการใช้งานสนามที่ไม่ได้รับมอบหมายผ่านการวิเคราะห์แบบคงที่ นี่คือจุดที่ฉันต้องการขยายในคำตอบนี้
ออกครั้งแรกสำหรับตัวแปรใด ๆ ในท้องถิ่นหรือมิฉะนั้นมันเป็นไปไม่ได้ในทางปฏิบัติเพื่อตรวจสอบว่าไม่ว่าจะเป็นตัวแปรที่ได้รับมอบหมายหรือยังไม่ได้กำหนด พิจารณา:
bool x;
if (M()) x = true;
Console.WriteLine(x);
คำถาม "ได้รับมอบหมาย x?" เทียบเท่ากับ "M () ส่งคืนจริงหรือไม่" ทีนี้สมมติว่า M () คืนค่าเป็นจริงถ้าทฤษฎีบทสุดท้ายของแฟร์มาต์เป็นจริงสำหรับจำนวนเต็มทั้งหมดที่น้อยกว่าสิบเอ็ดล้านล้านและเป็นเท็จ เพื่อพิจารณาว่า x ได้รับมอบหมายอย่างแน่นอนผู้แปลต้องสร้างหลักฐานบททฤษฎีบทสุดท้ายของแฟร์มาต์ คอมไพเลอร์ไม่ใช่สมาร์ทตัวนั้น
ดังนั้นสิ่งที่คอมไพเลอร์ทำแทนคนในท้องถิ่นจึงใช้อัลกอริธึมที่รวดเร็วและประเมินค่าเกินจริงเมื่อไม่ได้กำหนดท้องถิ่น นั่นคือมันมีผลบวกบางอย่างที่มันบอกว่า "ฉันไม่สามารถพิสูจน์ได้ว่ามีการกำหนดท้องถิ่น" แม้ว่าคุณและฉันรู้ว่ามันเป็น ตัวอย่างเช่น:
bool x;
if (N() * 0 == 0) x = true;
Console.WriteLine(x);
สมมติว่า N () ส่งคืนเลขจำนวนเต็ม คุณและฉันรู้ว่า N () * 0 จะเป็น 0 แต่คอมไพเลอร์ไม่ทราบว่า (หมายเหตุ: ที่ C # 2.0 คอมไพเลอร์ไม่ทราบว่า แต่ฉันออกเพิ่มประสิทธิภาพที่เป็นสเปคไม่ได้บอกว่าคอมไพเลอร์รู้ว่า.)
เอาล่ะเรารู้อะไรไปแล้ว มันเป็นไปไม่ได้สำหรับคนในท้องถิ่นที่จะได้รับคำตอบที่แน่นอน แต่เราสามารถประเมินค่าสูงเกินไปที่ไม่ได้รับมอบหมาย - อย่างถูกและได้รับผลลัพธ์ที่ดีงามที่ errs ด้าน "ทำให้คุณแก้ไขโปรแกรมที่ไม่ชัดเจน" ดีแล้ว. ทำไมไม่ทำแบบเดียวกันกับสาขา? นั่นคือให้ตรวจสอบการกำหนดที่ชัดเจนว่าการประเมินค่าสูงเกินไปอย่างถูก?
มีวิธีกี่วิธีในการที่จะเริ่มต้นในพื้นที่? มันสามารถได้รับมอบหมายภายในข้อความของวิธีการ มันสามารถได้รับมอบหมายภายในแลมบ์ดาในข้อความของวิธีการนั้น แลมบ์ดานั้นอาจไม่เคยถูกเรียกดังนั้นการมอบหมายเหล่านั้นจึงไม่เกี่ยวข้อง หรือมันสามารถถูกส่งผ่านเป็น "out" ไปยังวิธี anothe ซึ่งเป็นจุดที่เราสามารถสันนิษฐานได้ว่ามันถูกกำหนดเมื่อวิธีการส่งกลับตามปกติ เหล่านี้คือจุดที่ชัดเจนมากที่ท้องถิ่นได้รับมอบหมายและพวกเขาจะมีสิทธิในวิธีการเดียวกับที่มีการประกาศในท้องถิ่น กำหนดมอบหมายแน่นอนสำหรับชาวบ้านต้องใช้เพียงการวิเคราะห์ท้องถิ่น วิธีการมีแนวโน้มที่จะสั้น - น้อยกว่าหนึ่งล้านบรรทัดของรหัสในวิธี - และดังนั้นการวิเคราะห์วิธีทั้งหมดค่อนข้างรวดเร็ว
แล้วฟิลด์ล่ะ ฟิลด์สามารถเริ่มต้นได้ในตัวสร้างของหลักสูตร หรือตัวเริ่มต้นฟิลด์ หรือตัวสร้างสามารถเรียกวิธีการอินสแตนซ์ที่เริ่มต้นเขตข้อมูล หรือผู้สร้างสามารถเรียกวิธีเสมือนที่เริ่มต้นเขตข้อมูล หรือตัวสร้างสามารถเรียกใช้เมธอดในคลาสอื่นซึ่งอาจอยู่ในไลบรารีซึ่งจะเริ่มต้นฟิลด์ ฟิลด์แบบคงที่สามารถเริ่มต้นได้ในตัวสร้างแบบคงที่ ฟิลด์สแตติกสามารถเริ่มต้นได้โดยตัวสร้างสแตติกอื่น ๆ
โดยพื้นฐานแล้ว initializer สำหรับฟิลด์อาจอยู่ที่ใดก็ได้ในโปรแกรมทั้งหมดรวมถึงวิธีเสมือนภายในที่จะประกาศในไลบรารีที่ยังไม่ได้เขียน :
// Library written by BarCorp
public abstract class Bar
{
// Derived class is responsible for initializing x.
protected int x;
protected abstract void InitializeX();
public void M()
{
InitializeX();
Console.WriteLine(x);
}
}
มันเป็นข้อผิดพลาดในการรวบรวมห้องสมุดนี้หรือไม่? ถ้าใช่ BarCorp จะแก้ไขข้อผิดพลาดได้อย่างไร? โดยการกำหนดค่าเริ่มต้นให้กับ x? แต่นั่นคือสิ่งที่คอมไพเลอร์ทำไปแล้ว
สมมติว่าห้องสมุดนี้ถูกกฎหมาย ถ้า FooCorp เขียน
public class Foo : Bar
{
protected override void InitializeX() { }
}
คือว่ามีข้อผิดพลาด? คอมไพเลอร์ควรจะคิดออกยังไง? วิธีเดียวคือการทำวิเคราะห์โปรแกรมทั้งหมดที่ติดตามคงเริ่มต้นของทุกสาขาในเส้นทางที่เป็นไปได้ทุกผ่านโปรแกรมรวมทั้งเส้นทางที่เกี่ยวข้องกับทางเลือกของวิธีการเสมือนที่รันไทม์ ปัญหานี้อาจจะยากโดยพล ; มันสามารถเกี่ยวข้องกับการดำเนินการจำลองเส้นทางควบคุมหลายล้านรายการ การวิเคราะห์โฟลว์ควบคุมท้องถิ่นใช้เวลาไมโครวินาทีและขึ้นอยู่กับขนาดของวิธีการ การวิเคราะห์กระแสการควบคุมทั่วโลกอาจใช้เวลาเป็นชั่วโมงเพราะขึ้นอยู่กับความซับซ้อนของทุกวิธีในโปรแกรมและไลบรารีทั้งหมด
เหตุใดจึงไม่ทำการวิเคราะห์ที่ถูกกว่าซึ่งไม่จำเป็นต้องวิเคราะห์โปรแกรมทั้งหมดและแค่ประเมินค่าอย่างรุนแรงยิ่งขึ้น ขอเสนออัลกอริทึมที่ใช้งานได้ไม่ยากเกินไปที่จะเขียนโปรแกรมที่ถูกต้องซึ่งคอมไพล์จริงและทีมออกแบบสามารถพิจารณาได้ ฉันไม่รู้อัลกอริธึมดังกล่าว
ตอนนี้ commenter แนะนำ "ต้องการให้ตัวสร้างเริ่มต้นทุกฟิลด์" นั่นไม่ใช่ความคิดที่เลว ในความเป็นจริงมันเป็นความคิดที่ไม่เลวที่C # มีคุณสมบัติสำหรับ structsอยู่แล้ว ตัวสร้าง struct จำเป็นต้องกำหนดเขตข้อมูลทั้งหมดอย่างแน่นอนตามเวลาที่ ctor ส่งคืนตามปกติ ตัวสร้างเริ่มต้นเริ่มต้นเขตข้อมูลทั้งหมดเป็นค่าเริ่มต้นของพวกเขา
แล้วคลาสล่ะล่ะ? ดีอย่างไรคุณรู้ว่าคอนสตรัคได้เริ่มต้นสนาม ? ctor สามารถเรียกวิธีเสมือนเพื่อเริ่มต้นฟิลด์และตอนนี้เรากลับมาอยู่ในตำแหน่งเดียวกันกับที่เราเคยทำมาก่อน Structs ไม่มีคลาสที่ได้รับมา คลาสอาจ ห้องสมุดที่มีคลาสนามธรรมจำเป็นต้องมีคอนสตรัคเตอร์ที่เริ่มต้นฟิลด์ทั้งหมดหรือไม่? คลาสนามธรรมรู้ได้อย่างไรว่าค่าใดที่ควรเริ่มต้นกับฟิลด์
จอห์นแนะนำเพียงแค่ห้ามวิธีการโทรใน ctor ก่อนที่จะมีการเริ่มต้นเขตข้อมูล ดังนั้นสรุปตัวเลือกของเราคือ:
ทีมออกแบบเลือกตัวเลือกที่สาม
bool x;
เทียบเท่ากับbool x = false;
วิธีใด ๆ
เมื่อฉันสร้างบูลเดียวกันภายในวิธีการตรวจสอบบูล (แทนที่จะเป็นคลาส) ฉันได้รับข้อผิดพลาด "ใช้การตรวจสอบตัวแปรภายในที่ไม่ได้กำหนด" ทำไม?
เพราะคอมไพเลอร์พยายามที่จะป้องกันไม่ให้คุณทำผิดพลาด
การกำหนดค่าเริ่มต้นให้กับตัวแปรของคุณเพื่อfalse
เปลี่ยนแปลงอะไรในเส้นทางการดำเนินการนี้โดยเฉพาะหรือไม่ อาจไม่ได้พิจารณาว่าdefault(bool)
เป็นเท็จ แต่ก็บังคับให้คุณตระหนักว่าสิ่งนี้เกิดขึ้น สภาพแวดล้อม. NET ป้องกันไม่ให้คุณเข้าถึง "หน่วยความจำขยะ" เนื่องจากมันจะเริ่มต้นค่าใด ๆ ให้เป็นค่าเริ่มต้น แต่ถึงกระนั้นลองจินตนาการว่านี่เป็นประเภทอ้างอิงและคุณจะส่งค่าที่ไม่ได้กำหนดค่าเริ่มต้น (null) ไปยังวิธีที่คาดหวังว่าไม่ใช่ค่าว่างและรับ NRE เมื่อใช้งานจริง คอมไพเลอร์พยายามเพียงแค่ป้องกันไม่ให้ยอมรับความจริงที่ว่าบางครั้งอาจส่งผลให้เกิดbool b = false
คำสั่ง
Eric Lippert พูดถึงสิ่งนี้ในบล็อกโพสต์ :
เหตุผลที่เราต้องการทำให้สิ่งผิดกฎหมายนี้ไม่ได้เป็นอย่างที่หลายคนเชื่อเพราะตัวแปรท้องถิ่นกำลังเริ่มต้นกับขยะและเราต้องการปกป้องคุณจากขยะ ในความเป็นจริงเราจะเริ่มต้นคนในพื้นที่โดยอัตโนมัติเป็นค่าเริ่มต้น (แม้ว่าภาษาการเขียนโปรแกรม C และ C ++ ไม่ได้และจะช่วยให้คุณอ่านขยะจากเครื่องที่ไม่มีการกำหนดค่าภายในเครื่อง) แต่เป็นเพราะการมีอยู่ของรหัสเส้นทางนั้นอาจเป็นข้อผิดพลาดและเราต้องการทิ้งคุณไว้ใน หลุมคุณภาพ คุณควรทำงานอย่างหนักเพื่อเขียนบั๊กนั้น
เหตุใดจึงไม่ใช้กับเขตข้อมูลคลาส ฉันคิดว่าจะต้องลากเส้นที่ไหนสักแห่งและการเริ่มต้นตัวแปรท้องถิ่นนั้นง่ายกว่ามากในการวินิจฉัยและทำให้ถูกต้องตรงข้ามกับฟิลด์คลาส คอมไพเลอร์สามารถทำสิ่งนี้ได้ แต่คิดว่าการตรวจสอบที่เป็นไปได้ทั้งหมดนั้นจะต้องมีการทำ (ที่บางส่วนของพวกเขาเป็นอิสระจากรหัสชั้นเรียนของตัวเอง) เพื่อประเมินว่าแต่ละเขตข้อมูลในชั้นเรียนจะเริ่มต้น ฉันไม่ใช่ผู้ออกแบบคอมไพเลอร์ แต่ฉันแน่ใจว่ามันจะยากขึ้นแน่นอนเนื่องจากมีหลายกรณีที่ต้องนำมาพิจารณาและต้องทำในเวลาที่เหมาะสมเช่นกัน สำหรับทุกคุณสมบัติที่คุณต้องออกแบบเขียนทดสอบและปรับใช้และคุณค่าของการใช้สิ่งนี้ซึ่งต่างจากความพยายามที่ใส่เข้าไปนั้นไม่คุ้มค่าและซับซ้อน
เหตุใดตัวแปรโลคอลจึงต้องมีการเริ่มต้น แต่ฟิลด์ทำไม่ได้?
คำตอบสั้น ๆ คือรหัสที่เข้าถึงตัวแปรท้องถิ่นที่ไม่ได้กำหนดค่าเริ่มต้นสามารถตรวจพบได้โดยคอมไพเลอร์ในวิธีที่เชื่อถือได้โดยใช้การวิเคราะห์แบบคงที่ ในขณะที่นี่ไม่ใช่กรณีของเขตข้อมูล คอมไพเลอร์บังคับคดีแรก แต่ไม่ใช่กรณีที่สอง
ทำไมตัวแปรท้องถิ่นถึงต้องการการเริ่มต้น
นี่คือไม่มากไปกว่าการตัดสินใจการออกแบบของภาษา C # เป็นอธิบายโดยเอริค Lippert สภาพแวดล้อม CLR และ. NET ไม่จำเป็นต้องใช้ ยกตัวอย่างเช่น VB.NET จะคอมไพล์ได้ดีกับตัวแปรเฉพาะที่ไม่ได้กำหนดค่าเริ่มต้นและในความเป็นจริง CLR จะกำหนดค่าเริ่มต้นให้กับตัวแปรทั้งหมดที่ไม่ใช่ค่าเริ่มต้น
สิ่งเดียวกันอาจเกิดขึ้นกับ C # แต่นักออกแบบภาษาเลือกที่จะไม่ทำ เหตุผลก็คือตัวแปรที่กำหนดค่าเริ่มต้นเป็นแหล่งใหญ่ของข้อบกพร่องและด้วยการมอบอำนาจเริ่มต้นคอมไพเลอร์ช่วยลดความผิดพลาดโดยไม่ตั้งใจ
เหตุใดเขตข้อมูลจึงไม่ต้องการการเริ่มต้น
เหตุใดการเริ่มต้นอย่างชัดเจนไม่ได้เกิดขึ้นกับเขตข้อมูลภายในคลาส เนื่องจากการกำหนดค่าเริ่มต้นอย่างชัดเจนนั้นอาจเกิดขึ้นในระหว่างการก่อสร้างผ่านคุณสมบัติที่ถูกเรียกโดย object initializer หรือแม้กระทั่งโดยวิธีการที่เรียกว่านานหลังจากเหตุการณ์ คอมไพเลอร์ไม่สามารถใช้การวิเคราะห์แบบสแตติกเพื่อตรวจสอบว่าทุกเส้นทางที่เป็นไปได้ผ่านโค้ดนำไปสู่ตัวแปรที่ถูกกำหนดค่าเริ่มต้นอย่างชัดเจนต่อหน้าเรา การเข้าใจผิดจะเป็นเรื่องน่ารำคาญเนื่องจากผู้พัฒนาสามารถทิ้งไว้ด้วยรหัสที่ถูกต้องซึ่งจะไม่รวบรวม ดังนั้น C # จะไม่บังคับใช้เลยและ CLR จะถูกทิ้งให้เริ่มต้นฟิลด์โดยอัตโนมัติเป็นค่าเริ่มต้นหากไม่ได้ตั้งค่าไว้อย่างชัดเจน
ประเภทคอลเลกชันคืออะไร
การบังคับใช้การกำหนดค่าเริ่มต้นของตัวแปรโลคอล C # นั้นมี จำกัด ซึ่งมักจะทำให้ผู้พัฒนาไม่สนใจ พิจารณารหัสสี่บรรทัดต่อไปนี้:
string str;
var len1 = str.Length;
var array = new string[10];
var len2 = array[0].Length;
บรรทัดที่สองของโค้ดจะไม่คอมไพล์เนื่องจากมันพยายามอ่านตัวแปรสตริงที่ไม่มีการกำหนดค่าเริ่มต้น บรรทัดที่สี่ของการคอมไพล์โค้ดนั้นใช้ได้แม้ตามที่array
ได้รับการกำหนดค่าเริ่มต้น แต่จะมีเฉพาะค่าเริ่มต้นเท่านั้น เนื่องจากค่าเริ่มต้นของสตริงเป็นโมฆะเราจึงได้รับข้อยกเว้น ณ เวลาทำงาน ทุกคนที่ใช้เวลาที่นี่กับ Stack Overflow จะรู้ว่าความไม่สอดคล้องกันของการเริ่มต้นอย่างชัดเจน / โดยนัยนี้นำไปสู่การที่ยอดเยี่ยมมากมาย "เหตุใดฉันจึงได้รับข้อผิดพลาด“ การอ้างอิงวัตถุที่ไม่ได้ตั้งค่า คำถาม
public interface I1 { string str {get;set;} }
int f(I1 value) { return value.str.Length; }
หากสิ่งนี้มีอยู่ในไลบรารีคอมไพเลอร์ไม่สามารถรู้ได้ว่าไลบรารีนั้นจะเชื่อมโยงกับสิ่งนั้นหรือไม่ดังนั้นset
จะมีการเรียกใช้ก่อนหน้าget
นั้นหรือไม่ฟิลด์ข้อมูลสำรองอาจไม่ถูกเตรียมใช้งานอย่างชัดเจน แต่จะต้องรวบรวมรหัสดังกล่าว
f
แต่ฉันจะไม่คาดหวังว่าข้อผิดพลาดจะถูกสร้างขึ้นในขณะที่รวบรวม มันจะถูกสร้างขึ้นเมื่อรวบรวมการก่อสร้าง หากคุณปล่อยให้คอนสตรัคเตอร์ที่มีฟิลด์อาจไม่ได้กำหนดค่าเริ่มต้นนั่นจะเป็นข้อผิดพลาด อาจต้องมีข้อ จำกัด ในการเรียกคลาสเมธอดและ getters ก่อนที่ฟิลด์ทั้งหมดจะเริ่มต้นได้
คำตอบที่ดีด้านบน แต่ฉันคิดว่าฉันโพสต์คำตอบที่ง่ายกว่า / สั้นกว่ามากสำหรับคนที่ขี้เกียจอ่านอันยาวเหยียด (เหมือนตัวเอง)
class Foo {
private string Boo;
public Foo() { /** bla bla bla **/ }
public string DoSomething() { return Boo; }
}
คุณสมบัติBoo
อาจมีหรือไม่มีการเตรียมใช้งานในตัวสร้าง ดังนั้นเมื่อพบว่าreturn Boo;
มันไม่คิดว่ามันถูกเริ่มต้น มันเพียงแค่ระงับข้อผิดพลาด
public string Foo() {
string Boo;
return Boo; // triggers error
}
{ }
ตัวอักษรกำหนดขอบเขตของบล็อกของรหัสที่ คอมไพเลอร์เดินตามกิ่งก้านของ{ }
บล็อกเหล่านี้เพื่อติดตามสิ่งของ สามารถบอกได้อย่างง่ายดายBoo
ว่าไม่ได้เริ่มต้น ข้อผิดพลาดจะถูกเรียกใช้แล้ว
ข้อผิดพลาดถูกนำมาใช้เพื่อลดจำนวนบรรทัดของรหัสที่จำเป็นในการทำให้ซอร์สโค้ดปลอดภัย โดยไม่มีข้อผิดพลาดข้างต้นจะมีลักษณะเช่นนี้
public string Foo() {
string Boo;
/* bla bla bla */
if(Boo == null) {
return "";
}
return Boo;
}
จากคู่มือ:
คอมไพเลอร์ C # ไม่อนุญาตให้ใช้ตัวแปรที่ไม่กำหนดค่าเริ่มต้น หากคอมไพเลอร์ตรวจพบการใช้ตัวแปรที่อาจไม่ได้เริ่มต้นมันจะสร้างข้อผิดพลาดของคอมไพเลอร์ CS0165 สำหรับข้อมูลเพิ่มเติมโปรดดูฟิลด์ (คู่มือการเขียนโปรแกรม C #) โปรดทราบว่าข้อผิดพลาดนี้ถูกสร้างขึ้นเมื่อคอมไพเลอร์พบโครงสร้างที่อาจส่งผลให้การใช้ตัวแปรที่ไม่ได้กำหนดแม้ว่ารหัสของคุณไม่ได้ สิ่งนี้หลีกเลี่ยงความจำเป็นของกฎที่ซับซ้อนเกินไปสำหรับการกำหนดที่แน่นอน
การอ้างอิง: https://msdn.microsoft.com/en-us/library/4y7h161d.aspx