'Confusing behaviour when handling exceptions in combination with using

Question

I am currently implementing a UOW pattern and have come upon a weird behaviour for exception handling, which is hindering my progress.

My UOW structure looks the following:

public abstract class UnitOfWork : IDisposable {
    public virtual void Save() {
        if (_madeAction) ThrowInvalidCall();
        _madeAction = true;
    }
    public virtual void Cancel() {
        if (_madeAction) ThrowInvalidCall();
        _madeAction = true;
    }
    public virtual void Dispose() {
        if (!_madeAction) {
            Cancel();
            throw new UnitOfWorkAborted("Unit of work was aborted and automatically rolled back!");
        }
    }
    private bool _madeAction = false;
    private void ThrowInvalidCall() => throw new InvalidOperationException($"{nameof(Save)} or {nameof(Cancel)} can only be called once in a unit of work!");
}

[Serializable]
public class UnitOfWorkAborted : Exception {
    public UnitOfWorkAborted() { }
    public UnitOfWorkAborted(string message) : base(message) { }
    public UnitOfWorkAborted(string message, Exception inner) : base(message, inner) { }
}

public interface IUnitOfWorkFactory {
    UnitOfWork CreateNew();
}

I am expecting to use this UOW as follows:

try {
    using (var uow = uowFactory.CreateNew()) {
        // Do some operation
        throw new InvalidCastException(); // Oh, something went wrong.
        uow.Save();
    }
} catch (UnitOfWorkAborted ex) {
    // Handle exception
}

The problem obviously is, that the excpetion will never be handled by my try/catch clause as it only handles UnitOfWorkAborted which is only thrown after the InvalidCastException.

My question is, is there any way I can use my UOW how I expect it to work? (I'd like to replicate this behaviour -> TransactionScope)

I want to keep the code for creating a UOW and managing it as simple as possible.

If possible, I would love to even have the actual exception as the inner exception of UnitOfWorkAborted.

Observations/Attempts

1. Instead of catching the UnitOfWorkAborted I can catch all Exceptions and cast it to UnitOfWorkAborted.
try {
    using (var uow = uowFactory.CreateNew()) {
        throw new InvalidCastException();
        uow.Save();
    }
} catch (Exception ex) {
    UnitOfWorkAborted uowEx = ex as UnitOfWorkAborted;
    if (uowEx is null) throw ex;

    // Handle exception
}
  • Cons:
    • I will need to cast the exception to UnitOfWorkAborted and this adds code that should be avoidable because:
    • What is try/catch then even for when not for specifying which exception to handle? This approach just feels.. ugh.
2. Add a catch clause for Exception.
try {
    using (var uow = uowFactory.CreateNew()) {
        throw new InvalidCastException();
        uow.Save();
    }
} catch (UnitOfWorkAborted ex) {
    // Handle exception
} catch (Exception ex) {
    throw ex;
}

I discovered this through experimentation, it works perfectly fine. Would it be possible to get a side-explanation on the details for why this works, I would be incredibly interested to know. Either way, the syntax is incredibly misleading, even worse than with the first attempt, and this is no option of course, just, look at it.

Are these two attempts really my only options on solving this problem?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source