6
\$\begingroup\$

I'm currently working through a series of bugs in an application. Our application is written in C#/ASP.NET (on the server) and HTML/CSS/JavaScript (on the client). We are using ELMAH to log any uncaught exceptions in the system so that we may identify it later and fix it, which is what I'm currently doing.

The way ELMAH works is to log any uncaught exception, so once you catch and handle the exception, it can be omitted from ELMAH.

This article closely relates to a question I asked here on Stack Overflow.

To give a little bit of insight into this article, ELMAH logs uncaught exceptions, but sometimes you have certain scenarios where the exception is thrown to the browser and handled there. ELMAH doesn't know that it's being handled elsewhere, so it still logs the exception.

In our application, we use WCF web services to send JSON data back to the client. Say for example one of those services fails and throws an exception, we send that exception back to the browser and handle it there (and as stated, ELMAH still logs it).

So I had this idea, which might only apply to a small set of use cases, but as an idea, I wanted to know what the community thought of it.

As OO programmers know, methods/functions have a return type (or void).

Example

public void SayHello()
{
  Print("Hello");
}

public string GetMyName()
{
  return "Joe Bloggs";
}

All well and good so far, but what if I wanted my method/function to return either a value, or an exception if one was to occur? I can do this in C# using the dynamic keyword.

Example

public dynamic GetMyNameOrCryLikeABaby()
{
  try
  {
      return DoSomethingWrong(); // might throw an exception, but should return a string.
  }
  catch(Exception ex)
  {
      return ex;
  }
}

Personally I don't like this approach. I don't think it is a good use of the dynamic keyword, and it involves the developer adding in additional code to check whether the return value was (in this example) a string, or an Exception.

The idea I came up with to get around this was based somewhat on .NET's Nullable<T> structure. The idea here being that a ValueType wrapped in a Nullable<T> (e.g. Nullable<int>) can return null.

The structure below has some interesting characteristics! The aim was to write a structure that could implicitly be cast to the expected type, but could also return an exception, should one occur. This structure is called "Exceptable" meaning it might hold an exception.

Exceptable<T> Source Code

/// <summary>
/// Represents a generic type that can also hold an associated exception.
/// </summary>
/// <typeparam name="T">The type of this Exceptable object.</typeparam>
public struct Exceptable<T>
{
    /// <summary>
    /// Gets or sets the value of this object.
    /// </summary>
    public T Value { get; set; }

    /// <summary>
    /// Gets or sets an exception that may occur when using this type.
    /// </summary>
    public Exception Exception { get; set; }

    /// <summary>
    /// Gets a value indicating whether this object has a value.
    /// </summary>
    public Boolean HasValue
    {
        get { return this.Value != null; }
    }

    /// <summary>
    /// Gets a value indicating whether this object has an exception.
    /// </summary>
    public Boolean HasException
    {
        get
        {
            return this.Exception != null;
        }
    }

    /// <summary>
    /// Allows implicit casting between a raw type, and an Exceptable type.
    /// </summary>
    /// <param name="value">The exceptable value to convert to a raw type.</param>
    /// <returns>A raw type</returns>
    public static implicit operator T(Exceptable<T> value)
    {
        return value.Value;
    }

    /// <summary>
    /// Allows implicit casting between an Exceptable type and a raw type.
    /// </summary>
    /// <param name="value">The raw type to convert to an excepable type.</param>
    /// <returns>An excepable type.</returns>
    public static implicit operator Exceptable<T>(T value)
    {
        return new Exceptable<T>
        {
            Value = value
        };
    }
}

So as you can see here, it wraps the expected type into Exceptable as a generic type and provides implicit conversion between Exceptable<T> and T.

Use Cases

Exceptable<string> name = "Joe Bloggs";
Exceptable<int> age = 26;

public static Exceptable<string> GetMyNameOrCryLikeABaby()
{
    Exceptable<string> result = String.Empty;
    try
    {
        result = DoSomethingWrong() // might throw an exception but should return a string.
    }
    catch(Exception ex)
    {
        result.Exception = ex;
    }
    return result;
}

string name = GetMyNameOrCryLikeABaby();
Exceptable<string> nameOrException = GetMyNameOrCryLikeABaby();

As you can see from the examples above, I can assign values directly to Exceptable<T>. I can also use it as a return type for a method, and due to implicit conversion, I can choose whether to return just the associated value of the structure, or the structure itself, in case I want to handle the associated exception.

What do you think?

\$\endgroup\$
3
  • \$\begingroup\$ Interesting - you seem to have independently re-invented the error monad - well done sir! \$\endgroup\$
    – MattDavey
    Commented Jul 22, 2013 at 13:40
  • \$\begingroup\$ @MattDavey Technically, this is not a monad, since it doesn't have bind, or anything similar. \$\endgroup\$
    – svick
    Commented Jul 22, 2013 at 13:46
  • \$\begingroup\$ See this question and see if it helps. \$\endgroup\$
    – Bobson
    Commented Jul 22, 2013 at 14:58

3 Answers 3

7
\$\begingroup\$
  1. Doing this instead of working with exceptions the usual way (throw and catch) is a big code smell. I think I would need a really good justification for this and I'm not sure slightly improved logging is that. I would try looking more into other possibilities, like filtering Exceptions based on their type, or something like that.

  2. This code comes close to reinventing the Exception monad. You might want to study that for some ideas.

  3. Your type should protect its invariants. If it's supposed to contain a value or an exception, but not both, you shouldn't allow the user to use the type the wrong way.

    A similar principle also applies to reading: if a value is not present, Value should throw an exception, instead of silently returning the default value. The same probably applies to Exception.

  4. For a simple type like this, it often makes sense to make it immutable. Also, mutable value types are evil, you should never use them without a really good reason (and I don't see that reason here).

  5. You should be very careful with implicit conversions. An implicit conversion should never lead to loss of information and it should be always valid. In your case, that's not true for the case when Exceptable contains an exception, because then the implicit conversion silently returns the default value.

    So, if you want to have a conversion from Exceptable<T> to T, it should be explicit and it should throw when an exception is present.

  6. You're checking Value against null. This is a bad idea for several reasons:

    1. In some cases, null can be a legitimate return value that does not indicate an error. Your type won't handle that correctly.

    2. If T is a non-nullable value type, then the default value is not null, so HasValue won't work correctly. If your type is meant to support only reference types, you should make that explicit by adding the constraint where T : class to it.

  7. The name “exceptable” is not great. Though I'm not sure what would be a better name.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for this response. Based on your comments, I'm feeling that there is very little scope to use this...so maybe worth scrapping (but leaving here for future reference). \$\endgroup\$ Commented Jul 22, 2013 at 14:48
  • \$\begingroup\$ @svick - Item #7 - How about "Exceptionable" ? \$\endgroup\$ Commented Jul 22, 2013 at 16:04
1
\$\begingroup\$

I think that you are setting yourself up for horrible spaghetti code down the line if you don't catch and throw exceptions like the Lord intended.

If you absolutely INSIST on returning an error, I would suggest adding two OUT parameters to the methods:

string myFunkyFunction(string arg, out bool hasException, out string exceptionMessage/*or you could pass an actual Exception if that's your cup of tea*/)
{
    try{
        //code here
    }
    catch(Exception ex)
    {
        hasException = true;
        exceptionMessage = ex.Message;
    }

    if(!hasException)
    {
        hasException = false;
        exceptionMessage = "";
    }
    return "something";

}

then, IF there is an exception, do something.

\$\endgroup\$
4
  • \$\begingroup\$ I think it's a bad idea to use just string instead of Exception, Exception can contain quite a lot useful data. And the hasException parameter is not useful, the exception parameter should be null if there is no exception (certainly not "") and checking that is enough. \$\endgroup\$
    – svick
    Commented Jul 22, 2013 at 13:12
  • \$\begingroup\$ Well, yes, Exception is more useful, which is why I put that in a comment. Also, Visual Studio will complain if you don't assign something to an out parameter. \$\endgroup\$ Commented Jul 22, 2013 at 14:34
  • \$\begingroup\$ I meant that having out Exception exception would be better than string. And you have to assign something, but that something can be null. \$\endgroup\$
    – svick
    Commented Jul 22, 2013 at 15:17
  • \$\begingroup\$ fair enough. I did mention the option of returning an exception in the comment to the right of the method signature. \$\endgroup\$ Commented Jul 22, 2013 at 15:18
1
\$\begingroup\$

I suggest you don't need methods like HasValue() & HasException() hardcoded in it as they can be checked by the caller when receiving an response or may create an extension to it later to make it extendable.

But as this is for logging then you can add Source and Message which can be useful.So Info & Trace has a place called Message and not just Exception

So you can simple define Exceptable as

public class Exceptable<T>
{
    //The T Result will be set as actual response
    public T Value { get; private set; }
    
    //Message will hold any message even exception message
    public string Message { get;  set; }
    
    //InnerException to hold the exception and return back to the caller
    public Exception Exception { get;  set; }
    
    //Source will be the source method who is returning the value like class.method
    public string Source { get;  set; }
    
    public static implicit operator T(Exceptable<T> value)
    {
        return value.Value;
    }
    
    public static implicit operator Exceptable<T>(T value)
    {
        return new Exceptable<T>
        {
            Value = value
        };
    }
}

Then it can be used as

public static Exceptable<string> GetMyNameOrCryLikeABaby(bool throwException)
{
    Exceptable<string> result = String.Empty;
    try
    {
        if(throwException) throw new Exception("some exception");
        result = "Joe Bloggs";
    }
    catch(Exception ex)
    {
        result.Exception = ex;
    }
    return result;
}

Exceptable<int> age = 26;   
Exceptable<string>  name = GetMyNameOrCryLikeABaby(false);
Exceptable<string> nameOrException = GetMyNameOrCryLikeABaby(true);

Then you can created an extension to it later

public static class ExceptableExtensions
{
    public static bool IsValueEmpty(this Exceptable<string> exp)
    {
        if(exp.Value == string.Empty)
            return true;
        
        return false;
    }
    
    public static bool IsValueNull(this Exceptable<string> exp)
    {
        if(exp.Value == null)
            return true;
        
        return false;
    }
    
    public static bool IsLessThen(this Exceptable<int> exp, int checkvalue)
    {
        if(exp.Value < checkvalue)
            return true;
        
        return false;
    }
    
    public static bool HasException<T>(this Exceptable<T> exp)
    {
        if(exp.Exception != null)
            return true;
        
        return false;
    }
}

and use it as per your code.

age.IsLessThen(50);
name.HasException();

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.