Click here to Skip to main content
13,002,676 members (56,419 online)
Click here to Skip to main content
Add your own
alternative version

Stats

39.3K views
34 bookmarked
Posted 26 Aug 2015

Error Handling in SOLID C# .NET – The Operation Result Approach

, 26 Aug 2015
Rate this:
Please Sign up or sign in to vote.
Error handling often brings down an otherwise good design, this article offers an approach to standardize and simplify your error handling particularly in SOLID applications.

Problem

Say we have our beautifully engineered, following SOLID principals, FileStorageService class which is an implementation of IStorageService:

public interface IStorageService
{
    void WriteAllBytes(string path, byte[] buffer);
    byte[] ReadAllBytes(string path);
}

public class FileStorageService : IStorageService
{
    public void WriteAllBytes(string path, byte[] buffer)

    {
        File.WriteAllBytes(path, buffer);
    }

    public byte[] ReadAllBytes(string path)
    {
        return File.ReadAllBytes(path);
    }
}

What if calling one of the methods resulted in an Exception? As we know reading and writing to files can result in a litany of Exceptions such as: IOException, DirectoryNotFoundException, FileNotFoundException, UnauthorizedAccessException… and of course the ever possible OutOfMemoryException (a real concern reading all bytes from a file).

Solution 1 – Not My Problem

We could not care about it, say it is the responsibility of the caller. Problem with that is since the caller is (should be) using an IStorageService how should it know what Exceptions the run time implementation could throw? Moreover the run time implementation could change in future and as a result the Exceptions it could throw. The lazy solution to this is just to wrap your call in catch-all exception handler:

IStorageService myStorageService = Resolver.Resolve<IStorageService>();
try
{
    myStorageService.ReadAllBytes("C:\stuff.data");
}

catch (Exception exception)
{
    // please don't write generic error messages like this, be specific
    Logger.Log("Oops something went wrong: " + exception.Message);
}

Hopefully we already know why catching Exception is bad. Also everyone using IStorageService has to wrap every call in their own Exception handling.

Solution 2 – Create new Exception Type

Perhaps an improvement upon the previous solution would be to create our own Exception Type, say: StorageReadException which we could throw in our implementation when only an Exception we expect and can deal with occurs. Then the caller can safely only handle StorageReadException regardless of the of the runtime implementation, e.g.:

public class StorageReadException : Exception
{
    public StorageReadException(Exception innerException)
        : base(innerException.Message, innerException)
    {
    }
}

And in our `FileStorageService` from earlier:

public byte[] ReadAllBytes(string path)
{
    try
    {
        return File.ReadAllBytes(path);
    }
    catch (FileNotFoundException fileNotFoundException)
    {
        throw new StorageReadException(fileNotFoundException);
    }
}

And our caller could:

IStorageService myStorageService = Resolver.Resolve<IStorageService>();
try
{
    myStorageService.ReadAllBytes(path);
}
catch (StorageReadException sre)
{
    Logger.Log(String.Format("Failed to read file from path, {0}: {1}", path, sre.Message));
}

Issues I have with his approach are: we have to create a new Type consumers may be unfamiliar with and the caller still has to write a try catch block every call.

Solution 3 – Try Pattern with Complex Result

IStorageService operations can fail, i.e. it is not exceptional to fail, let’s alleviate our consumers having to write their own Exception handling every time by using the Try pattern – the same pattern on int (Int32) when parsing a string: bool TryParse(string s, out int result). We could change 

byte[] ReadAllBytes(string path)

to:

bool TryReadAllBytes(string path, out byte[] result)

But that does not give us much information such as why the operation failed; perhaps we want to show a message to the user giving them some information about the failure - and IStorageService cannot be expected to show a messages as thats not its responsibility. So instead of bool lets’ return a new Type containing: whether the operation was a success, the result of the operation if was successful otherwise a message why not and details about the Exception that caused the failure which the caller can choose to use, introducing OperationResult<TResult>:

public class OperationResult<TResult>
{
    private OperationResult ()
    {
    }

    public bool Success { get; private set; }
    public TResult Result { get; private set; }
    public string NonSuccessMessage { get; private set; }
    public Exception Exception { get; private set; }

    public static OperationResult<TResult> CreateSuccessResult(TResult result)
    {
        return new OperationResult<TResult> { Success = true, Result = result};
    }

    public static OperationResult<TResult> CreateFailure(string nonSuccessMessage)
    {
        return new OperationResult<TResult> { Success = false, NonSuccessMessage = nonSuccessMessage};
    }

    public static OperationResult<TResult> CreateFailure(Exception ex)
    {
        return new OperationResult<TResult>
        {
            Success = false,
            NonSuccessMessage = String.Format("{0}{1}{1}{2}", ex.Message, Environment.NewLine, ex.StackTrace),
            Exception = ex
        };
    }
}

(I used a private constructor so one of the Create() methods have to be used ensuring Result has to be set if successful; otherwise NonSuccessMessage has to be supplied if not). We can change FileStorageService’s ReadAllBytes() method (and the interface) to:

public OperationResult<byte[]> TryReadAllBytes(string path)
{
    try
    {
        var bytes = File.ReadAllBytes(path);
        return OperationResult<byte[]>.CreateSuccessResult(bytes);
    }
    catch (FileNotFoundException fileNotFoundException)
    {
        return OperationResult<byte[]>.CreateFailure(fileNotFoundException);
    }
}

And the calling code becomes:

var result = myStorageService.TryReadAllBytes(path);
if(result.Success)
{
    // do something
}
else
{
    Logger.Log(String.Format("Failed to read file from path, {0}: {1}", path, result.NonSuccessMessage));
}

That’s it! Now if an Exception we cannot handle occurs it bubbles as it should; if an expected Exception occurred in the implementation we can use the information on the retuned OperationResult.

OperationResult<TResult> can be used across the API on all operations that can fail.

History

27 August 2015 - Inital Version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

markmnl
Zimbabwe Zimbabwe
No Biography provided

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 4 Pin
Thomas Nielsen - MIRSK23-Sep-15 1:36
memberThomas Nielsen - MIRSK23-Sep-15 1:36 
GeneralRe: My vote of 4 Pin
markmnl23-Sep-15 15:06
membermarkmnl23-Sep-15 15:06 
AnswerRe: My vote of 4 Pin
Thomas Nielsen - MIRSK23-Sep-15 20:50
memberThomas Nielsen - MIRSK23-Sep-15 20:50 
GeneralRe: My vote of 4 Pin
markmnl23-Sep-15 21:06
membermarkmnl23-Sep-15 21:06 
AnswerRe: My vote of 4 Pin
Thomas Nielsen - MIRSK23-Sep-15 22:13
memberThomas Nielsen - MIRSK23-Sep-15 22:13 
GeneralVery well explained Pin
Ehsan Sajjad16-Sep-15 7:08
memberEhsan Sajjad16-Sep-15 7:08 
QuestionNot recommended Pin
Godrose5-Sep-15 3:24
memberGodrose5-Sep-15 3:24 
AnswerRe: Not recommended Pin
markmnl5-Sep-15 17:09
membermarkmnl5-Sep-15 17:09 
GeneralRe: Not recommended Pin
Godrose23-Dec-15 21:07
memberGodrose23-Dec-15 21:07 
General6 of this or half dozen of the other Pin
Member 100827673-Sep-15 6:01
memberMember 100827673-Sep-15 6:01 
GeneralRe: 6 of this or half dozen of the other Pin
markmnl3-Sep-15 16:35
membermarkmnl3-Sep-15 16:35 
GeneralI prefer solution #2 Pin
John Brett2-Sep-15 21:16
memberJohn Brett2-Sep-15 21:16 
QuestionIt follows Go principles Pin
Leonid Ganeline31-Aug-15 5:28
professionalLeonid Ganeline31-Aug-15 5:28 
AnswerRe: It follows Go principles Pin
markmnl31-Aug-15 15:02
membermarkmnl31-Aug-15 15:02 
Suggestionsyntactic sugar Pin
John Torjo30-Aug-15 21:14
memberJohn Torjo30-Aug-15 21:14 
GeneralRe: syntactic sugar Pin
markmnl30-Aug-15 21:20
membermarkmnl30-Aug-15 21:20 
GeneralRe: syntactic sugar Pin
John Torjo1-Sep-15 23:16
memberJohn Torjo1-Sep-15 23:16 
GeneralRe: syntactic sugar Pin
danielibarnes10-Sep-15 9:17
memberdanielibarnes10-Sep-15 9:17 
GeneralRe: syntactic sugar Pin
John Torjo10-Sep-15 9:22
professionalJohn Torjo10-Sep-15 9:22 
GeneralRe: syntactic sugar Pin
danielibarnes10-Sep-15 10:30
memberdanielibarnes10-Sep-15 10:30 
GeneralRe: syntactic sugar Pin
John Torjo10-Sep-15 11:10
professionalJohn Torjo10-Sep-15 11:10 
QuestionExtremely dangerous approach - there are much better pattern for handling exceptions Pin
svenmatzen30-Aug-15 3:22
membersvenmatzen30-Aug-15 3:22 
AnswerRe: Extremely dangerous approach - there are much better pattern for handling exceptions Pin
markmnl30-Aug-15 14:51
membermarkmnl30-Aug-15 14:51 
GeneralRe: Extremely dangerous approach - there are much better pattern for handling exceptions Pin
Member 1194678030-Aug-15 20:22
memberMember 1194678030-Aug-15 20:22 
GeneralRe: Extremely dangerous approach - there are much better pattern for handling exceptions Pin
markmnl30-Aug-15 20:36
membermarkmnl30-Aug-15 20:36 
GeneralRe: Extremely dangerous approach - there are much better pattern for handling exceptions Pin
markmnl30-Aug-15 20:48
membermarkmnl30-Aug-15 20:48 
GeneralRe: Extremely dangerous approach - there are much better pattern for handling exceptions Pin
AndySchuppeschampoo30-Aug-15 21:25
memberAndySchuppeschampoo30-Aug-15 21:25 
GeneralRe: Extremely dangerous approach - there are much better pattern for handling exceptions Pin
markmnl30-Aug-15 21:39
membermarkmnl30-Aug-15 21:39 
GeneralRe: Extremely dangerous approach - there are much better pattern for handling exceptions Pin
James Lonero8-Sep-15 12:42
memberJames Lonero8-Sep-15 12:42 
Question[My vote of 1] Extremely bad practice! Pin
Mikhael Ray Burton28-Aug-15 17:32
memberMikhael Ray Burton28-Aug-15 17:32 
AnswerRe: [My vote of 1] Extremely bad practice! Pin
markmnl1-Sep-15 22:29
membermarkmnl1-Sep-15 22:29 
AnswerRe: [My vote of 1] Extremely bad practice! Pin
Godrose5-Sep-15 3:46
memberGodrose5-Sep-15 3:46 
AnswerRe: [My vote of 1] Extremely bad practice! Pin
James Lonero8-Sep-15 12:58
memberJames Lonero8-Sep-15 12:58 
Question[My vote of 2] You chose the anti-pattern Pin
Kirk Wood28-Aug-15 9:29
memberKirk Wood28-Aug-15 9:29 
AnswerRe: [My vote of 2] You chose the anti-pattern Pin
danielibarnes28-Aug-15 10:18
memberdanielibarnes28-Aug-15 10:18 
AnswerRe: [My vote of 2] You chose the anti-pattern Pin
Member 1170024728-Aug-15 14:18
memberMember 1170024728-Aug-15 14:18 
AnswerRe: [My vote of 2] You chose the anti-pattern Pin
markmnl28-Aug-15 17:00
membermarkmnl28-Aug-15 17:00 
AnswerRe: [My vote of 2] You chose the anti-pattern Pin
markmnl28-Aug-15 17:19
membermarkmnl28-Aug-15 17:19 
GeneralMy vote of 5 Pin
rlovelacecap28-Aug-15 7:14
professionalrlovelacecap28-Aug-15 7:14 
Questionmust check every single calls Pin
Member 1019550528-Aug-15 2:36
professionalMember 1019550528-Aug-15 2:36 
AnswerRe: must check every single calls Pin
markmnl28-Aug-15 17:02
membermarkmnl28-Aug-15 17:02 
GeneralRe: must check every single calls Pin
svenmatzen30-Aug-15 3:24
membersvenmatzen30-Aug-15 3:24 
GeneralRe: must check every single calls Pin
markmnl30-Aug-15 14:40
membermarkmnl30-Aug-15 14:40 
GeneralMy vote of 5 Pin
_Vitor Garcia_28-Aug-15 1:42
member_Vitor Garcia_28-Aug-15 1:42 
QuestionI like it Pin
malcolm clarke28-Aug-15 1:14
membermalcolm clarke28-Aug-15 1:14 
QuestionNice and simple Pin
Oliver Schoenbeck27-Aug-15 4:37
memberOliver Schoenbeck27-Aug-15 4:37 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun27-Aug-15 1:15
memberHumayun Kabir Mamun27-Aug-15 1:15 
GeneralGood Alternate Solution for Error Handling Pin
Ashok Kumar RV26-Aug-15 22:35
memberAshok Kumar RV26-Aug-15 22:35 
GeneralRe: Good Alternate Solution for Error Handling Pin
DaveMertens28-Aug-15 11:27
memberDaveMertens28-Aug-15 11:27 
GeneralRe: Good Alternate Solution for Error Handling Pin
markmnl28-Aug-15 17:14
membermarkmnl28-Aug-15 17:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170626.1 | Last Updated 27 Aug 2015
Article Copyright 2015 by markmnl
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid