การใช้ IDisposable และ "ใช้" เป็นวิธีการในการรับ "พฤติกรรมที่กำหนดขอบเขต" เพื่อความปลอดภัยในทางที่ผิดหรือไม่


112

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

ใน C # ฉันมักจะต้องการทำ

class FrobbleManager
{
    ...

    private void FiddleTheFrobble()
    {
        this.Frobble.Unlock();
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
        this.Frobble.Lock();
    }
}

ซึ่งต้องทำแบบนี้

private void FiddleTheFrobble()
{
    this.Frobble.Unlock();

    try
    {            
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
    }
    finally
    {
        this.Frobble.Lock();
    }
}

ถ้าฉันต้องการรับประกันFrobbleสถานะเมื่อใดFiddleTheFrobbleส่งคืน รหัสจะดีกว่าด้วย

private void FiddleTheFrobble()
{
    using (var janitor = new FrobbleJanitor(this.Frobble))
    {            
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
    }
}

ที่FrobbleJanitorดูคร่าวๆชอบ

class FrobbleJanitor : IDisposable
{
    private Frobble frobble;

    public FrobbleJanitor(Frobble frobble)
    {
        this.frobble = frobble;
        this.frobble.Unlock();
    }

    public void Dispose()
    {
        this.frobble.Lock();
    }
}

และนั่นคือวิธีที่ฉันต้องการทำ ตอนนี้ความเป็นจริงจับได้แล้วเนื่องจากสิ่งที่ฉันต้องการใช้ต้องการให้FrobbleJanitorใช้กับ usingกับฉันคิดว่านี่เป็นปัญหาการตรวจสอบโค้ด แต่มีบางอย่างทำให้ฉันรำคาญ

คำถาม:ข้างต้นจะถือเป็นการใช้usingและIDisposableหรือไม่?


23
+1 สำหรับชื่อคลาสและเมธอดเพียงอย่างเดียว :-) เพื่อความยุติธรรมและคำถามที่แท้จริง
Joey

2
@ โจฮันเนส: ใช่ฉันเข้าใจแล้วว่าทำไมคนส่วนใหญ่แค่เล่นซอ แต่ฉันก็ต้องประท้วงเรื่องนั้น ;-) และยังไงก็ตาม +1 สำหรับความคล้ายคลึงกันของชื่อคุณ
Johann Gerell

บางทีคุณอาจใช้ขอบเขตการล็อก (this) {.. } เพื่อให้ได้ผลลัพธ์เดียวกันในตัวอย่างนี้
ถึง

1
@ oɔɯǝɹ: คุณใช้ lock () เพื่อป้องกันการเข้าถึงบางสิ่งจากเธรดที่แตกต่างกันพร้อมกัน ไม่เกี่ยวข้องกับสถานการณ์ที่ฉันอธิบาย
Johann Gerell

1
ใช้งานได้โดยไม่มี Finalizer ใน C # เวอร์ชันถัดไป :)
Jon Raynor

คำตอบ:


33

ฉันไม่คิดอย่างนั้นจำเป็น ในทางเทคนิค IDisposable มีไว้เพื่อใช้สำหรับสิ่งที่มีทรัพยากรที่ไม่ได้รับการจัดการ แต่แล้วคำสั่งการใช้เป็นเพียงวิธีที่เรียบร้อยในการนำรูปแบบทั่วไปของtry .. finally { dispose }.

คนเจ้าระเบียบจะโต้เถียงว่า 'ใช่ - มันไม่เหมาะสม' และในแง่ของคนเจ้าระเบียบก็คือ; แต่พวกเราส่วนใหญ่ไม่ได้เขียนโค้ดจากมุมมองของคนเจ้าระเบียบ แต่มาจากแนวกึ่งศิลปะ การใช้โครงสร้าง 'ใช้' ในลักษณะนี้ค่อนข้างเป็นศิลปะในความคิดของฉัน

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

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


2
ผมเชื่อว่าเป็นการ "ใช้" อย่างมีศิลปะเช่นกัน ฉันคิดว่าโพสต์ของ Samual Jack ที่ลิงก์ไปยังความคิดเห็นจาก Eric Gunnerson จะตรวจสอบการใช้งาน "โดยใช้" นี้ ฉันเห็นคะแนนที่ยอดเยี่ยมจากทั้ง Andras และ Eric ในเรื่องนี้
IAbstract

1
ฉันได้พูดคุยกับ Eric Gunnerson เกี่ยวกับเรื่องนี้มาระยะหนึ่งแล้วและตามที่เขาพูดมันเป็นความตั้งใจของทีมออกแบบ C # ที่ใช้จะแสดงขอบเขต ในความเป็นจริงเขาตั้งสมมติฐานว่าพวกเขาออกแบบโดยใช้ก่อนคำสั่งล็อคอาจไม่มีแม้แต่คำสั่งล็อค มันน่าจะเป็นการใช้บล็อกที่มีการโทรตรวจสอบหรืออะไรบางอย่าง UPDATE: เพิ่งรู้คำตอบต่อไปคือโดย Eric Lippert ซึ่งอยู่ในทีมภาษาด้วย ;) บางทีทีม C # เองก็ไม่เห็นด้วยกับเรื่องนี้? บริบทของการสนทนาของฉันกับ Gunnerson คือคลาสTimedLock
Haacked

2
@ แฮ็ก - น่าขบขันไม่ใช่เหรอ ความคิดเห็นของคุณพิสูจน์ประเด็นของฉันโดยสิ้นเชิง ที่คุณพูดกับผู้ชายคนหนึ่งในทีมและเขาก็ทำเพื่อมันทั้งหมด จากนั้นชี้ให้เห็น Eric Lippert ด้านล่างจากทีมเดียวกันไม่เห็นด้วย จากมุมมองของฉัน; C # ให้คำหลักที่ใช้ประโยชน์จากอินเทอร์เฟซและยังให้รูปแบบรหัสที่ดูดีซึ่งใช้งานได้ในหลายสถานการณ์ หากเราไม่ควรละเมิด C # ก็ควรหาวิธีอื่นในการบังคับใช้สิ่งนั้น ไม่ว่าจะด้วยวิธีใดก็ตามนักออกแบบอาจจะต้องเอาเป็ดมาเรียงกันที่นี่! ในระหว่างนี้using(Html.BeginForm())ครับท่าน? ไม่เป็นไรถ้าฉันทำ! :)
Andras Zoltan

71

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

ฉันถือว่านี่เป็นการละเมิดด้วยเหตุผลสามประการ

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

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

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

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

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

การใช้บล็อก "ใช้" เพื่อให้มีผลทางความหมายทำให้ส่วนของโปรแกรมนี้:

}

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

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

bool needsLock = false;
try
{
    // must be carefully written so that needsLock is set
    // if and only if the unlock happened:

    this.Frobble.AtomicUnlock(ref needsLock);
    blah blah blah
}
finally
{
    if (needsLock) this.Frobble.Lock();
}

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


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

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

7
โพสต์ที่ยอดเยี่ยมนี่คือสองเซ็นต์ของฉัน :) ฉันสงสัยว่า "การละเมิด" มากusingเป็นเพราะมันเป็นแง่มุมของช่างทอผ้าที่มุ่งเน้นไปที่ C # สิ่งที่นักพัฒนาต้องการจริงๆคือวิธีการรับประกันว่าโค้ดทั่วไปบางส่วนจะทำงานในตอนท้ายของบล็อกโดยไม่ต้องทำซ้ำรหัสนั้น C # ไม่รองรับ AOP ในวันนี้ (MarshalByRefObject & PostSharp ไม่ทน) อย่างไรก็ตามการเปลี่ยนแปลงของคอมไพลเลอร์ของคำสั่งใช้ทำให้สามารถบรรลุ AOP อย่างง่ายได้โดยการกำหนดความหมายของการกำจัดทรัพยากรที่กำหนดขึ้นใหม่ แม้ว่าจะไม่ใช่แนวทางปฏิบัติที่ดี แต่บางครั้งก็น่าสนใจที่จะผ่านไป .....
LBushkin

11
แม้ว่าฉันจะเห็นด้วยกับตำแหน่งของคุณโดยทั่วไป แต่ก็มีบางกรณีที่ประโยชน์ของการเสนอใหม่usingนั้นน่าดึงดูดเกินกว่าที่จะยอมแพ้ ตัวอย่างที่ดีที่สุดที่ฉันคิดได้คือการติดตามและรายงานการกำหนดเวลาของโค้ด: using( TimingTrace.Start("MyMethod") ) { /* code */ } อีกครั้งนี่คือ AOP - Start()จับเวลาเริ่มต้นก่อนที่บล็อกจะเริ่มขึ้นDispose()จับเวลาสิ้นสุดและบันทึกกิจกรรม ไม่เปลี่ยนสถานะของโปรแกรมและไม่เชื่อเรื่องพระเจ้ากับข้อยกเว้น นอกจากนี้ยังน่าสนใจเนื่องจากหลีกเลี่ยงการวางท่อประปาและการใช้งานบ่อยครั้งเป็นรูปแบบช่วยลดความหมายที่สับสน
LBushkin

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

27

หากคุณต้องการโค้ดที่สะอาดและมีขอบเขตคุณอาจใช้ lambdas, á la

myFribble.SafeExecute(() =>
    {
        myFribble.DangerDanger();
        myFribble.LiveOnTheEdge();
    });

ที่.SafeExecute(Action fribbleAction)วิธีการห่อtry- catch- finallyบล็อก


ในตัวอย่างนั้นคุณพลาดสถานะการเข้า นอกจากนี้ให้แน่ใจว่าคุณได้ลอง / ในที่สุด แต่คุณไม่ได้เรียกอะไรเลยในที่สุดดังนั้นหากมีบางสิ่งในแลมบ์ดาพ่นคุณก็ไม่รู้ว่าคุณอยู่ในสถานะใด
Johann Gerell

1
อา! โอเคฉันเดาว่าคุณหมายถึงนั่นSafeExecuteเป็นFribbleวิธีการโทรUnlock()และLock()ในการลองแบบห่อ / ในที่สุด ฉันขอโทษแล้ว ฉันเห็นทันทีว่าSafeExecuteเป็นวิธีการขยายทั่วไปดังนั้นจึงกล่าวถึงการขาดสถานะการเข้าและออก ความผิดฉันเอง.
Johann Gerell

1
ระมัดระวังกับ apporach นี้ อาจมีการขยายอายุการใช้งานโดยไม่ได้ตั้งใจที่เป็นอันตรายในกรณีที่ชาวบ้านถูกจับ!
สัน

4
ยูค. ทำไมต้องซ่อน try / catch / ในที่สุด? ทำให้รหัสของคุณซ่อนไม่ให้คนอื่นอ่าน
Frank Schwieterman

2
@ ยูคกี้แฟรงค์: ไม่ใช่ความคิดของฉันที่จะ "ซ่อน" อะไรเลย แต่เป็นคำขอของผู้ถาม :-P ... ที่กล่าวว่าสุดท้ายแล้วคำขอคือ "อย่าทำซ้ำตัวเอง" คุณอาจมีวิธีการมากมายที่ต้องใช้ "เฟรม" ต้นแบบเดียวกันในการแสวงหา / ปล่อยบางสิ่งอย่างหมดจดและคุณไม่ต้องการกำหนดสิ่งนั้นให้กับผู้โทร (นึกถึงการห่อหุ้ม) นอกจากนี้ยังสามารถตั้งชื่อเมธอดเช่น. SafeExecute (... ) ให้ชัดเจนยิ่งขึ้นเพื่อสื่อสารสิ่งที่พวกเขาทำได้ดีพอ
herzmeister

25

Eric Gunnerson ซึ่งอยู่ในทีมออกแบบภาษา C # ให้คำตอบสำหรับคำถามเดียวกันนี้:

Doug ถาม:

เรื่องคำสั่งล็อกกับการหมดเวลา ...

ฉันเคยทำเคล็ดลับนี้มาก่อนแล้วเพื่อจัดการกับรูปแบบทั่วไปในหลาย ๆ วิธี มักจะล็อคเข้าซื้อกิจการแต่มีบางคนอื่น ๆ ปัญหาคือมักจะรู้สึกเหมือนแฮ็กเนื่องจากวัตถุไม่ได้ถูกทิ้งจริงๆมากเท่ากับ " call-back-at-the-end-of-a-scope-able "

ดั๊ก

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


4
คำพูดนั้นควรจะพูดในสิ่งที่เขาอ้างถึงเมื่อเขาพูดว่า "สถานการณ์นี้" เพราะฉันสงสัยว่าเขากำลังอ้างถึงคำถามในอนาคตนี้
Frank Schwieterman

4
@ Frank Schwieterman: ฉันกรอกใบเสนอราคา เห็นได้ชัดว่าผู้คนจากทีม C # ไม่คิดว่าusingคุณลักษณะที่ได้รับไม่ได้ถูก จำกัด ให้การกำจัดของทรัพยากร
paercebal

11

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

void UseMeUnlocked(Action callback) {
  Unlock();
  try {
    callback();
  }
  finally {
    Lock();
  }
}

แต่นั่นมีแนวโน้มที่จะอึดอัดเล็กน้อยหากไม่มี lamdas ที่กล่าวว่าฉันใช้ IDisposable เหมือนที่คุณทำ

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

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

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


"herzmeister der welten" ข้างต้นฉันคิดว่า - แม้ว่า OP จะไม่เหมือนตัวอย่างก็ตาม
Benjamin Podszun

ใช่ฉันตีความว่าเขาSafeExecuteเป็นวิธีการขยายทั่วไป ตอนนี้ฉันเห็นว่ามันน่าจะหมายถึงFribbleวิธีการที่เรียกUnlock()และLock()ในการลองแบบห่อ / ในที่สุด ความผิดฉันเอง.
Johann Gerell

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

7

ตัวอย่างในโลกแห่งความเป็นจริงคือ BeginForm ของ ASP.net MVC โดยทั่วไปคุณสามารถเขียน:

Html.BeginForm(...);
Html.TextBox(...);
Html.EndForm();

หรือ

using(Html.BeginForm(...)){
    Html.TextBox(...);
}

Html.EndForm เรียก Dispose และ Dispose เพียงแค่แสดงผล</form>แท็ก ข้อดีของเรื่องนี้คือ {} วงเล็บจะสร้าง "ขอบเขต" ที่มองเห็นได้ซึ่งทำให้ง่ายต่อการมองเห็นสิ่งที่อยู่ในแบบฟอร์มและสิ่งที่ไม่

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

โดยส่วนตัวฉันจะใช้มันก็ต่อเมื่อกฎสองข้อต่อไปนี้เป็นจริง แต่ฉันตั้งค่าโดยพลการ:

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

ในตอนท้ายมันเป็นเรื่องของความคาดหวัง หากวัตถุใช้ IDisposable ฉันคิดว่ามันต้องทำการล้างข้อมูลบางอย่างฉันจึงเรียกมันว่า ฉันคิดว่ามันมักจะเต้นด้วยฟังก์ชั่น "ปิดเครื่อง"

ที่ถูกกล่าวว่าฉันไม่ชอบบรรทัดนี้:

this.Frobble.Fiddle();

ในขณะที่ FrobbleJanitor "เป็นเจ้าของ" Frobble ในตอนนี้ฉันสงสัยว่ามันจะไม่ดีกว่าที่จะเรียก Fiddle ว่าเขา Frobble in the Janitor แทน?


เกี่ยวกับ "ฉันไม่ชอบบรรทัดนี้" ของคุณ - จริงๆแล้วฉันคิดสั้น ๆ ว่าจะทำแบบนั้นเมื่อตั้งคำถาม แต่ฉันไม่ต้องการให้ความหมายของคำถามของฉันยุ่งเหยิง แต่ฉันเห็นด้วยกับคุณ ชนิดของ. :)
Johann Gerell

1
ได้รับการโหวตให้เป็นตัวอย่างจาก Microsoft เอง โปรดทราบว่ามีข้อยกเว้นเฉพาะที่จะเกิดขึ้นในกรณีที่คุณกล่าวถึง: ObjectDisposedException
Antitoon

4

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


4

Chiming ในเรื่องนี้: ฉันเห็นด้วยกับที่นี่มากที่สุดว่ามันเปราะบาง แต่มีประโยชน์ ฉันต้องการชี้ให้คุณไปที่คลาสSystem.Transaction.TransactionScopeซึ่งทำสิ่งที่คุณต้องการทำ

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


1
"ภารโรง" มาจากบทความ C ++ เก่าของ Andrei Alexandrescu เกี่ยวกับ Scope Guard ก่อนที่ ScopeGuard จะมาหา Loki
Johann Gerell

@ เบนจามิน: ฉันชอบคลาส TransactionScope ไม่เคยได้ยินมาก่อน อาจเป็นเพราะฉันอยู่ใน. Net CF land โดยที่มันไม่รวม ... :-(
Johann Gerell

4

หมายเหตุ: มุมมองของฉันอาจมีความลำเอียงจากพื้นหลัง C ++ ของฉันดังนั้นค่าของคำตอบของฉันควรได้รับการประเมินเทียบกับอคติที่เป็นไปได้ ...

ข้อกำหนดภาษา C # คืออะไร?

การอ้างอิงข้อกำหนดภาษา C # :

8.13 คำสั่งการใช้

[... ]

ทรัพยากรเป็นชั้นเรียนหรือ struct ว่าการดำเนินการ System.IDisposable ซึ่งรวมถึงวิธีการ parameterless เดียวชื่อทิ้ง โค้ดที่ใช้ทรัพยากรสามารถเรียก Dispose เพื่อระบุว่าทรัพยากรไม่จำเป็นอีกต่อไป หากไม่เรียกว่า Dispose การกำจัดโดยอัตโนมัติจะเกิดขึ้นในที่สุดเนื่องจากการเก็บขยะ

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

ฉันเดาว่านี่โอเคเพราะ Lock เป็นทรัพยากร

บางทีคำหลักอาจusingถูกเลือกไม่ดี scopedบางทีมันอาจจะควรจะได้รับการเรียกว่า

จากนั้นเราสามารถพิจารณาเกือบทุกอย่างเป็นทรัพยากร ที่จับไฟล์ การเชื่อมต่อเครือข่าย ... เธรด?

ด้าย ???

ใช้ (หรือใช้ในทางที่ผิด) usingคำหลัก?

มันจะเป็นเงาที่จะ (AB) ใช้usingคำหลักเพื่อให้แน่ใจว่าการทำงานของเธรดจะสิ้นสุดลงก่อนที่จะออกขอบเขต?

Herb Sutter ดูเหมือนจะคิดว่ามันเป็นประกายในขณะที่เขานำเสนอการใช้รูปแบบ IDispose ที่น่าสนใจเพื่อรอให้งานของเธรดสิ้นสุดลง:

http://www.drdobbs.com/go-parallel/article/showArticle.jhtml?articleID=225700095

นี่คือรหัสที่คัดลอกมาจากบทความ:

// C# example
using( Active a = new Active() ) {    // creates private thread
       
       a.SomeWork();                  // enqueues work
       
       a.MoreWork();                   // enqueues work
       
} // waits for work to complete and joins with private thread

แม้ว่าจะไม่มีการระบุรหัส C # สำหรับวัตถุที่ใช้งานอยู่ แต่ก็มีการเขียน C # ใช้รูปแบบ IDispose สำหรับรหัสที่มีเวอร์ชัน C ++ อยู่ในตัวทำลาย และจากการดูเวอร์ชัน C ++ เราจะเห็นตัวทำลายที่รอให้เธรดภายในสิ้นสุดก่อนที่จะออกดังที่แสดงในสารสกัดอื่น ๆ ของบทความนี้:

~Active() {
    // etc.
    thd->join();
 }

ดังนั้นเท่าที่เป็นสมุนไพรเป็นห่วงก็เป็นเงางาม


3

IDisposableผมเชื่อว่าคำตอบสำหรับคำถามของคุณคือไม่มีนี้จะไม่ละเมิดของ

วิธีที่ฉันเข้าใจIDisposableอินเทอร์เฟซคือคุณไม่ควรทำงานกับอ็อบเจ็กต์เมื่อถูกกำจัดไปแล้ว (ยกเว้นว่าคุณได้รับอนุญาตให้เรียกDisposeใช้เมธอดได้บ่อยเท่าที่คุณต้องการ)

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

(Btw. โค้ดตัวอย่างมาตรฐานที่แสดงให้เห็นถึงการนำไปใช้อย่างเหมาะสมDisposeเกือบตลอดเวลาแนะนำว่าทรัพยากรที่มีการจัดการควรได้รับการปลดปล่อยเช่นกันไม่ใช่แค่ทรัพยากรที่ไม่มีการจัดการเช่นการจัดการระบบไฟล์)

สิ่งเดียวที่ฉันกังวลเป็นการส่วนตัวคือไม่มีความชัดเจนว่าเกิดอะไรขึ้นกับบล็อกที่using (var janitor = new FrobbleJanitor())ชัดเจนกว่าtry..finallyซึ่งสามารถมองเห็นLock& Unlockการดำเนินการได้โดยตรง แต่แนวทางใดที่ใช้อาจขึ้นอยู่กับความชอบส่วนบุคคล


1

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


1

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

ฉันจะเปลี่ยนชื่อ FrobbleJanitor แม้ว่าอาจจะเป็น FrobbleLockSession


เกี่ยวกับการเปลี่ยนชื่อ - ฉันใช้ "ภารโรง" เป็นเหตุผลหลักที่จัดการกับ Lock / Unlock ฉันคิดว่ามันคล้องจองกับตัวเลือกชื่ออื่น ๆ ;-) ถ้าฉันจะใช้วิธีนี้เป็นรูปแบบตลอดฐานรหัสของฉันฉันจะรวมบางสิ่งที่อธิบายโดยทั่วไปมากกว่านี้อย่างที่คุณบอกเป็นนัยกับ "เซสชัน" ถ้าเป็น C ++ สมัยก่อนฉันอาจจะใช้ FrobbleUnlockScope
Johann Gerell
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.