เป็นวิธีที่ดีในการโทรกลับภายในโดยใช้คำสั่ง {} หรือไม่


93

ฉันแค่อยากรู้ว่าวิธีการโทรreturnภายในusingบล็อกนั้นปลอดภัย / ดีไหม

สำหรับอดีต

using(var scope = new TransactionScope())
{
  // my core logic
  return true; // if condition met else
  return false;
  scope.Complete();
}

เรารู้ว่าในวงเล็บปีกกาสุดท้าย dispose()จะถูกเรียกออก แต่จะเกิดอะไรขึ้นในกรณีข้างต้นเนื่องจากreturnข้ามการควบคุมออกจากขอบเขตที่กำหนด (AFAIK) ...

  1. ฉันscope.Complete()ถูกเรียก?
  2. และสำหรับdispose()วิธีการของขอบเขต

1
เมื่อusing{}ขอบเขตสิ้นสุดลงวัตถุที่เกี่ยวข้องจะถูกกำจัดออกreturnจะ "ทำลาย" ขอบเขต - ดังนั้นวัตถุจะถูกกำจัดตามที่คาดไว้
Shai

4
โปรดทราบว่าการscope.Complete()โทรของคุณจะไม่ถูกกระทบกับตัวอย่างที่คุณให้ไว้ดังนั้นธุรกรรมของคุณจะย้อนกลับเสมอ
Andy

โดยไม่คำนึงว่าusing's dispose()เรียกว่าเมื่อคุณกลับฟังก์ชั่นที่มีนี้usingบล็อกจะได้กลับมาและทุกอย่างที่อยู่ในนั้นจะถูกกำพร้า ดังนั้นแม้ว่าscopeจะไม่ได้รับการกำจัด "โดยusing" (จะเป็นไปตามที่คนอื่น ๆ อธิบายไว้) มันก็จะถูกกำจัดอยู่ดีเพราะฟังก์ชันสิ้นสุดลง ถ้า C # มีgotoคำสั่ง - คุณหัวเราะเสร็จหรือยัง? ดีแล้วแทนที่จะส่งคืนคุณสามารถทำได้gotoหลังจากปิดวงเล็บปีกกาโดยไม่ต้องกลับมา เหตุผลscopeก็ยังคงถูกกำจัด แต่คุณเพิ่งใส่gotoC # ดังนั้นใครสนใจตรรกะในขั้นตอนนั้น
Superbest

C # มีโกโตะ
Jake T.

คำตอบ:


146

การโทรreturnภายในusingบล็อกของคุณปลอดภัยอย่างสมบูรณ์เนื่องจากบล็อกที่ใช้เป็นเพียงtry/finallyบล็อก

ในตัวอย่างของคุณด้านบนหลังจากส่งคืนtrueขอบเขตจะถูกกำจัดและมูลค่าที่ส่งคืน return falseและscope.Complete()จะไม่ถูกเรียก Disposeอย่างไรก็ตามจะถูกเรียกโดยไม่คำนึงถึงเนื่องจากมันอยู่ในบล็อกสุดท้าย

โดยพื้นฐานแล้วรหัสของคุณจะเหมือนกับสิ่งนี้ (หากทำให้เข้าใจง่ายขึ้น):

var scope = new TransactionScope())
try
{
  // my core logic
  return true; // if condition met else
  return false;
  scope.Complete();
}
finally
{
  if( scope != null) 
    ((IDisposable)scope).Dispose();
}

โปรดทราบว่าธุรกรรมของคุณจะไม่มีวันผูกมัดเนื่องจากไม่มีทางที่scope.Complete()จะทำธุรกรรมได้


13
คุณควรพูดให้ชัดเจนว่าDispose จะถูกเรียก หาก OP ไม่ได้รู้ว่าสิ่งที่เกิดขึ้นในโอกาสที่เขาไม่ได้รู้ว่าสิ่งที่เกิดขึ้นกับusing finally
Konrad Rudolph

เป็นเรื่องปกติที่จะปล่อยให้บล็อกโดยใช้ผลตอบแทน แต่ในกรณีของ TransactionScope คุณอาจมีปัญหากับคำสั่งการใช้งานเอง: blogs.msdn.com/b/florinlazar/archive/2008/05/05/8459994.aspx
thewhiteambit

จากประสบการณ์ของฉันสิ่งนี้ใช้ไม่ได้กับ SQL Server CLR Assemblies เมื่อฉันต้องการส่งคืนผลลัพธ์สำหรับ UDF ที่มีฟิลด์ SqlXml ที่อ้างถึงวัตถุ MemoryStream ฉันได้รับ " ไม่สามารถเข้าถึงวัตถุที่จำหน่ายแล้ว " และ " ความพยายามที่ไม่ถูกต้องในการเรียกอ่านเมื่อสตรีมถูกปิด " ดังนั้นฉันจึงถูกบังคับให้เขียนโค้ดที่รั่วไหลและละทิ้งคำสั่งใช้ในสถานการณ์นี้ :( ความหวังเดียวของฉันคือ SQL CLR จะจัดการกับการกำจัดวัตถุเหล่านี้มันเป็นสถานการณ์ที่ไม่เหมือนใคร แต่คิดว่าฉันจะแบ่งปัน
MikeTeeVee

1
@MikeTeeVee - โซลูชั่นการทำความสะอาดเป็นอย่างใดอย่างหนึ่ง (ก) มีโทรทำusingเช่นusing (var callersVar = MyFunc(..)) .., แทนของการมีการใช้ภายใน "MyFunc" - ผมหมายถึงผู้ที่โทรมาจะได้รับกระแสและเป็นผู้รับผิดชอบสำหรับการปิดมันผ่านusingหรืออย่างชัดเจนหรือ (ข) มี MyFunc สารสกัดจากข้อมูลอะไรก็ตามที่เป็นสิ่งจำเป็นเป็นวัตถุอื่น ๆ ที่สามารถส่งกลับมาอย่างปลอดภัย - usingแล้ววัตถุข้อมูลหรือกระแสต้นแบบสามารถทิ้งโดยคุณ คุณไม่ควรต้องเขียนโค้ดรั่ว
ToolmakerSteve

7

ไม่เป็นไร - finallyประโยค (ซึ่งเป็นสิ่งที่วงเล็บปีกกาปิดของส่วนusingคำสั่งทำภายใต้ประทุน) จะถูกดำเนินการเสมอเมื่อขอบเขตถูกทิ้งไว้ไม่ว่าจะอย่างไร

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


2

โดยทั่วไปเป็นแนวทางที่ดี แต่ในกรณีของคุณถ้าคุณกลับมาก่อนที่จะโทรไปscope.Complete()มันก็จะทิ้ง TransactionScope ขึ้นอยู่กับการออกแบบของคุณ

ดังนั้นในตัวอย่างนี้จึงไม่มีการเรียก Complete () และขอบเขตจะถูกกำจัดโดยสมมติว่ามันกำลังสืบทอดอินเทอร์เฟซ IDisposable


ต้องใช้ IDisposable หรือใช้การคอมไพล์แบบไม่ใช้งาน
Chriseyre2000

2

scope.Complete returnแน่นอนควรจะเรียกว่าก่อนที่จะ คอมไพเลอร์จะแสดงคำเตือนและจะไม่มีการเรียกรหัสนี้

เกี่ยวกับreturnตัวมันเอง - ใช่มันปลอดภัยที่จะเรียกมันว่าusingงบภายใน การใช้ถูกแปลว่าพยายามบล็อกในที่สุดหลังฉากและในที่สุดการบล็อกจะถูกดำเนินการ


1

ในตัวอย่างที่คุณให้มามีปัญหา scope.Complete()ไม่เคยถูกเรียก ประการที่สองก็ไม่ได้เป็นวิธีที่ดีในการใช้returnคำสั่งภายในusingงบ อ้างถึงสิ่งต่อไปนี้:

using(var scope = new TransactionScope())
{
    //have some logic here
    return scope;      
}

ในตัวอย่างง่ายๆนี้ประเด็นก็คือ ค่าของscopeจะเป็นโมฆะเมื่อใช้คำสั่งเสร็จสิ้น

ดังนั้นจึงเป็นการดีกว่าที่จะไม่กลับภายในโดยใช้งบ


1
เพียงเพราะ 'ขอบเขตการคืนสินค้า' ไม่มีจุดหมายจึงไม่ได้หมายความว่าคำสั่งส่งคืนนั้นไม่ถูกต้อง
เปรียญ

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

1
ค่าของscopeจะไม่เป็นโมฆะ - สิ่งเดียวที่จะเกิดขึ้นคือDispose()จะถูกเรียกใช้ในอินสแตนซ์นั้นดังนั้นจึงไม่ควรใช้อินสแตนซ์อีกต่อไป (แต่มันไม่เป็นโมฆะและไม่มีอะไรที่ป้องกันไม่ให้คุณลองใช้ วัตถุที่กำจัดแล้วแม้ว่าจะเป็นการใช้วัตถุที่ใช้แล้วทิ้งอย่างไม่เหมาะสมก็ตาม)
Lucero

Lucero ค่อนข้างถูกต้อง วัตถุที่ใช้แล้วทิ้งจะไม่เป็นโมฆะหลังจากกำจัดทิ้ง คุณสมบัติ IsDisposed มันเป็นความจริง แต่ถ้าคุณตรวจสอบกับ null คุณจะได้รับเท็จและreturn scopeกลับการอ้างอิงไปที่วัตถุ ด้วยวิธีนี้หากคุณกำหนดการอ้างอิงในการส่งคืนคุณจะป้องกันไม่ให้ GC ล้างข้อมูลวัตถุที่กำจัดทิ้ง
ThunderGr

1

เพื่อให้แน่ใจว่าจะถูกเรียกว่าห่อมันด้วยscope.Complete() try/finallyสิ่งdisposeนี้เรียกว่าเนื่องจากคุณได้ห่อด้วยบล็อกusingทางเลือกtry/finally

using(var scope = new TransactionScope())
{
  try
  {
  // my core logic
  return true; // if condition met else
  return false;
  }
  finally
  {
   scope.Complete();
  }
}

ฉันคิดว่าคุณอยากจะบอกว่าถ้าคุณชนะ - ถ้าคุณต้องการไม่ใช่จะไม่ ... :)

0

ในตัวอย่างนี้ scope.Complete () จะไม่ดำเนินการ อย่างไรก็ตามคำสั่ง return จะล้างข้อมูลทุกอย่างที่กำหนดบนสแตก GC จะดูแลทุกอย่างที่ไม่มีการอ้างอิง ดังนั้นเว้นแต่จะมีวัตถุที่ GC ไม่สามารถหยิบขึ้นมาได้ก็ไม่มีปัญหา

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