I have a proprietary C# library that is basically a wrapper around some C code. The functions it provides look like this:
public int func(in params, out params...)
The int returned signals success (0), some error code (<0) or some data (>0, e.g. number of bytes successfully read).
Additionally, the library provides a public EnumErrorCode GetLastError()
function
My task is to write yet another wrapper, that translates each function with error codes to function that throw exceptions
While it's easy to get the returncode into the exception
public class MyException : Exception
{
public int RetVal { get; }
public MyException(int retVal) : { RetVal = retVal;}
public MyException(string message, int retVal) : base(message) { RetVal = retVal; }
}
used like
public void func(int a)
{
var retVal = api.func(a);
switch (retVal)
{
case 0: return;
case -1: throw new MyException("same error across all funcs", retVal);
case -2: throw new MyException("func specific error", retVal);
default: throw new MyException(retVal);
}
}
I have trouble figuring out right way to also get the EnumErrorCode GetLastError() into the exception
First of all, should I introduce a new property public EnumErrorCode ErrorCode { get; }
, with EnumErrorCode being defined in the proprietary C# library or should I just store an int?
Secondly, is it ok to call GetLastError()
while throwing the exception?
case -1: throw new MyException("Mayhem!", retVal, GetLastError());
Doesn't seem right to make another call into foreign unknown code while throwing. What other possibilities are there?
Thanks Fildor for your detailed answer. I think I´ll mix in that function specific message (provided by the API-documentation) and use a dictionary for the EnumErrorCode mapping in the Create function
public interface IMyExceptionFactory
{
MyException Create(string funcSpecificCodeMsg, int returnCode, int errorCode);
}
// snip
int returnCode = CallToThirdParty();
if (returnCode < 0)
{
int errorCode = -1;
try
{
errorCode = (int)GetLastError();
}
catch (Exception) { }
switch (returnCode)
{
case -1: throw _exceptionFactory.Create("invalid handle", returnCode, errorCode);
case -2: throw _exceptionFactory.Create("func specific error -2", returnCode, errorCode);
...
default: throw _exceptionFactory.Create("Unknown returnCode", returnCode, errorCode);
}
}
CodePudding user response:
First: To reiterate points from comments:
- If you need that to be thread-safe, you'd need to make both calls "atomic" to your clients.
- I'd enclose
GetLastError
with another try/catch just to be safe.
Using the 3rd party Enum
This depends on much you want to hide the 3rd party from the clients of your adapter. If they shouldn't be aware (at least while using your adapter, of course, they would probably know there is a 3rd party) of the 3rd party, then you would need to hide the original Enum.
This could be done by making your own "copy" of that Enum. This is possible, but it has serious pitfalls: On each update of the 3rd party, you'd need to check if the enum has changed and change your code accordingly. Which could lead to breaking changes, even.
What I have done in the past is just using and exposing the integer value and providing a ErrorCodeToString
function that basically casts the int to the 3rd Party enum and then does a ToString
. That was enough for my purposes, but of course I don't claim that to be the or even a "best practice".
Creating Exceptions
Recap what we have:
- Functions can have return codes of
- 0 == "ok"
- >0 (only some of them) == some integer valued result.
- <0 , where ...
- '-1' is a common error shared by all functions
- '-2' and smaller are function-specific errors (different meaning per function)
- On top of negative returnCodes, an errorCode (=> enum) can be retrieved by calling
GetLastError
.
Now, one question would be: Is maintaining an exhausting table of all function-specific returncodes beneficial in addition to just having the ErrorCodeEnum value?
If yes: Things get a little more complicated. But maybe we can set this aside for now and focus on the Enum values.
Of course, you wouldn't want to write a custom handling for each and every function. So, what you want is some kind of Exception-Factory.
My assumptions regarding this are:
- You probably want to assign each enum value a different exception message.
- You've already shown a custom
MyException
type, so you'll probably want to use that.
Now, you need to make a design decision. You can use one exception type and have it have properties. Or you can derive "child" types from your base exception type.
The latter would make it convenient to group or single out exceptions you want/need to handle versus others you (or the client) want to just "bubble up".
Either way, you'll want to have a an interface like
public interface IMyExceptionFactory
{
MyException Create(int returnCode, int errorCode);
}
to use in your adapter (I guess you'll want to inject it), so it can do its magic, and you can throw whatever it gives you.
For example:
// snip
int returnCode = CallToThirdParty();
if( returnCode < 0 )
{
int errorCode = GetLastError();
throw _exceptionFactory.Create(returnCode, errorCode);
}
Inside its implementation, you'll build the appropriate Exception with Message (maybe a derived type) and other properties set to the correct values.
For example you could build a dictionary that lets you lookup messages by errorCodes etc. You could then decide to have that built hardcoded or through a configuration ...