How do I determine the HResult for a System.IO.IOException?
Asked Answered
A

6

37

The System.Exception.HResult property is protected. How can I peek inside an exception and get the HResult without resorting to reflection or other ugly hacks?


Here's the situation:
I want to write a backup tool, which opens and reads files on a system. I open the file with FileAccess.Read and FileShare.ReadWrite, according to this guidance, because I don't care if the file is open for writing at the time I read it.

In some cases, when a file I am reading is open by another app, the System.IO.FileStream.Read() method throws a System.IO.IOException, "The process cannot access the file because another process has locked a portion of the file". This is error 33, or I think HResult 0x80070021. [EDIT: I believe this can be returned when another process calls LockFileEx to lock a byte range within a file.]

I'd like to pause and retry when I get this error. I think this is the appropriate action to take here. If the locking process releases the byte-range lock quickly, then I can proceed reading the file.

How can I distinguish an IOException for this reason, from others? I can think of these ways:

  • private reflection - don't wanna do that. Perf will stink.
  • call Exception.ToString() and parse the string. Feels hacky. Won't work in i18n versions.

I don't like these options. Isn't there a better, cleaner way?


I just searched around and found System.Runtime.InteropServices.Marshal.GetHRForException. Will that return a uint like 0x80070021?

Adamek answered 13/6, 2009 at 21:23 Comment(1)
> private reflection - don't wanna do that. Perf will stink. - Exception perf stinks anyway, so I wouldn't worry about the perf aspect. Reflection does - however - require FullTrust, is ugly, and is unsupported and prone to breakage - which is why you shouldn't do it.Engenia
D
59

For .Net Framework 4.5 and above, you can use the Exception.HResult property:

int hr = ex.HResult;

For older versions, you can use Marshal.GetHRForException to get back the HResult, but this has significant side-effects and is not recommended:

int hr = Marshal.GetHRForException(ex);
Diophantus answered 13/6, 2009 at 22:25 Comment(4)
Thanks a lot! This really ease the development in case of C#/COM interoperability.Layton
+1 Eww, requires full trust... But it is a solution nonetheless.Seldom
Beware of side-effects: "Note that the GetHRForException method sets the IErrorInfo of the current thread. This can cause unexpected results for methods like the ThrowExceptionForHR methods that default to using the IErrorInfo of the current thread if it is set."Gallego
I strongly recommend you do not use GetHRForException. It caused a very strange problem that took us months to understand: https://mcmap.net/q/275620/-ioexception-raised-despite-ioexception-catch-blockExergue
S
11

For what it's worth, System.Exception.HResult is no longer protected in .NET 4.5 -- only the setter is protected. That doesn't help with code that might be compiled with more than one version of the framework.

Sardanapalus answered 3/5, 2013 at 18:15 Comment(0)
I
5

You can also use the ISerializable interface:

static class IOExceptionExtensions
{
    public static int GetHResult(this IOException ex)
    {
        var info = new SerializationInfo(typeof (IOException), new FormatterConverter());
        ex.GetObjectData(info, new StreamingContext());
        return info.GetInt32("HResult");
    }
}
Inhibition answered 19/11, 2015 at 19:2 Comment(0)
S
0

Does CanRead property help in this case?
i.e. call CanRead, if that returns true, call Read()

Scrummage answered 13/6, 2009 at 21:44 Comment(4)
Nope, CanRead is true. I believe 80070021 is a transient error. If I am reading the doc correctly, to handle it the recommended practice is to "wait a while and retry."Adamek
Is it possible that you opened the file for reading & someone else opened it as well (using FileShare.Read), the 1st caller can't read it anymore? Is that what you mean by transient?Scrummage
No, what I mean is, another process has called FileLock or FileLockEx (msdn.microsoft.com/en-us/library/aa365203.aspx) on the file to lock a range within the file. This is sometimes called a byte-range lock. At some point the locking process will release the range lock. That is what I mean by "Transient."Adamek
Thanks Cheeso. Coming from a VB background, I thought that file can be locked completely by 1 reader. Never thought that a range of characters can be locked for reading.Scrummage
F
0

Have you profiled either of these cases? I'd imagine that the reflection method isn't all that slow, especially relative to all the other works your app will be doing and how often this exception will be likely to occur.

If it turns out to be a bottleneck you can look into caching some of the reflection operations or generate dynamic IL to retrieve the property.

Foursquare answered 13/6, 2009 at 21:49 Comment(0)
P
0

Necromancing.
Or you can just fetch the protected property by reflection:

private static int GetHresult(System.Exception exception)
{
    int retValue = -666;

    try
    {
        System.Reflection.PropertyInfo piHR = typeof(System.Exception).GetProperty("HResult", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);

        if (piHR != null)
        {
            object o = piHR.GetValue(exception, null);
            retValue = System.Convert.ToInt32(o);
        }
    }
    catch (Exception ex)
    {
    }

    return retValue;
}
Pensive answered 25/10, 2019 at 14:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.