I am looking for advice as to how to handle any exceptions thrown in the following code example:
private string SendRequest() { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(myURL); // Code initialising HttpWebRequest HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream rcvdStream = response.GetResponseStream(); StreamReader readStream = new StreamReader(rcvdStream, Encoding.UTF8); string responseString = readStream.ReadToEnd(); response.Close(); readStream.Close(); return responseString; }
My main concern is to ensure the StreamReader and HttpRequest object are closed whent the method ends. Should I:
- Wrap the lot up in a try/catch/finally logging any exceptions in the catch block and closing the stream in the finally block?
- Use a using statement on the HttpWebRequest object instantiation and a nested using statement when creating the StreamReader?
- Not worry about it and assume GC will clear everything up when the objects go out of scope as the method is exited?
EDIT: Further investigation has revealed that option 2 can be done without nesting the using statements:
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) using (Stream rcvdStream = response.GetResponseStream()) { StreamReader readStream = new StreamReader(rcvdStream, Encoding.UTF8); payResponse = readStream.ReadToEnd(); }
This produces the following IL code which demonstrates that it is effectively creating a nested try/finally block:
IL_00b0: callvirt instance class [System]System.Net.WebResponse [System]System.Net.WebRequest::GetResponse() IL_00b5: castclass [System]System.Net.HttpWebResponse IL_00ba: stloc.s response .try { IL_00bc: ldloc.s response IL_00be: callvirt instance class [mscorlib]System.IO.Stream [System]System.Net.WebResponse::GetResponseStream() IL_00c3: stloc.s rcvdStream .try { IL_00c5: nop IL_00c6: ldloc.s rcvdStream IL_00c8: call class [mscorlib]System.Text.Encoding [mscorlib]System.Text.Encoding::get_UTF8() IL_00cd: newobj instance void [mscorlib]System.IO.StreamReader::.ctor(class [mscorlib]System.IO.Stream, class [mscorlib]System.Text.Encoding) IL_00d2: stloc.s readStream IL_00d4: ldloc.s readStream IL_00d6: callvirt instance string [mscorlib]System.IO.TextReader::ReadToEnd() IL_00db: stloc.3 IL_00dc: nop IL_00dd: leave.s IL_00f3 } // end .try finally { IL_00df: ldloc.s rcvdStream IL_00e1: ldnull IL_00e2: ceq IL_00e4: stloc.s CS$4$0001 IL_00e6: ldloc.s CS$4$0001 IL_00e8: brtrue.s IL_00f2 IL_00ea: ldloc.s rcvdStream IL_00ec: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_00f1: nop IL_00f2: endfinally } // end handler IL_00f3: nop IL_00f4: leave.s IL_010a } // end .try finally { IL_00f6: ldloc.s response IL_00f8: ldnull IL_00f9: ceq IL_00fb: stloc.s CS$4$0001 IL_00fd: ldloc.s CS$4$0001 IL_00ff: brtrue.s IL_0109 IL_0101: ldloc.s response IL_0103: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0108: nop IL_0109: endfinally } // end handler
Options one and two are your best bets. Option 3 IS NOT a good idea.
I am personally quite fond of the Using statement route. However, the try/catch/finally route is essentially the same, and provides you your needed logging mechanism.