4 surprises with asserts moving from Delphi to C#

QuestionMark_Exclamation-Point

Moving from Delphi to C# is fun most of the time.

Discovering stuff I can do in C# that is impossible, or (very) time consuming, in Delphi is fun. Discovering that stuff I take for granted in Delphi – i.e. metaclasses (officially “class references”), not having to re-declare constructors to use them to instantiate descendant classes – is impossible in C#, is less fun, but still interesting.

Sometimes I am just plain astonished, if not to say flabbergasted.

One such occurrence happened a couple of weeks ago. I was working on some code where I was putting a call to a validation method in a Debug.Assert to check for contract violations. I naively assumed that C#’s Debug.Assert would behave in a very similar manner to Assert in Delphi. And was completely taken off guard by the differences.

Assert in Delphi

In Delphi the Assert looks as follows. (The message part is optional.)

    Assert({boolean expression}False, 'Message used when the boolean expression evaluates to false.');

When you call Assert in Delphi and the boolean expression evaluates to false this raises (throws) an EAssertionFailed exception.

The beauty of an exception being raised is that it enables you to use Assert calls to ensure a method’s contract is met. The exception guarantees that execution won’t proceed beyond the Assert call when the contract is violated. Barring any except (catch) and finally blocks of course.

You can control whether assertions are compiled into an executable using an “Assert directive”

    {$C+} 
    // or 
    {$ASSERTIONS ON} 
    // to turn assertions on, and 
    {$C-} 
    // or 
    {$ASSERTIONS OFF}
    // to turn them off

As the Assert directive has local scope, you can keep some asserts even when all others are turned off. Using the {$IFOPT xxx} directive you can even create a construct that reinstates the project scope value after your local overrule.

This is the way my world has worked for the good many years that I have developed using Delphi.

Enter C#

And see my world turned on its head.

The Debug class is where you turn for Assert‘s.

Or is it?

Surprise 1. Two classes offering Assert

First surprise was that there are two classes you can use for assertions: Debug and Trace. The Assert overloads have the exact same functionality in both classes.

Why?

Apparently Debug.Assert the calls are automagically removed from assemblies compiled when the DEBUG conditional is not defined, while Trace.Assert calls are always compiled into an assembly.

And that was the second surprise.

Surprise 2. DEBUG conditional dictates inclusion and exclusion

The Delphi convention certainly is that you turn assertions on for debug builds and off for release builds, but there is no one holding a gun to your head.

In fact there can be many reasons to include some assertions in release builds, while excluding others.

For one, the code run to evaluate the boolean expression may have side effects that you do not want to miss out on. See Assertions in Managed Code for an example.

For another, some checks are more important than others. While you might be willing to forego the contract checks of most assertions, there are some checks where you want to guarantee that the code protected by that check is never executed if the contract was violated.

The choice in C# certainly seems more “elegant”: use Debug.Assert if you are ok with excluding the check, use Trace.Assert if you want to keep it. A lot easier and more readable than using IFDEF and/or IFOPT directives.

So, simply use Trace.Assert for any Assert you want to make it into your assembly regardless of whether you created a DEBUG or a release build?

Well … No.

Enter surprises number three.

Surprise 3. Asserts in C# bring up dialogs

According to the MSDN documentation, Debug.Assert:

Checks for a condition; if the condition is false, outputs messages and displays a message box that shows the call stack.

Scuse me? What the bleep?

How about Trace then? That is set to compile into release builds, so it wouldn’t really bring up a dialog now would it? Well, unfortunately, it does. The documentation for Trace.Assert is – letter by letter – exactly the same as for Debug.Assert.

Which means that if you are building servers and services, you’d better steer clear of Trace for protecting your code against contract violations. I wouldn’t want to be the developer that caused a sysop or devop to have to respond to a call in the middle of the night to close a dialog so the server/service can continue …

And even if there were some – as yet unknown to me – feature in .Net that would suppress the dialogs in code running as a Windows’ service, I still can’t use Trace to protect my code against contract violations.

And that was surprise number four.

Surprise 4. Asserts in C# do not throw exceptions

Having read the description in the docs for the Assert method of both the Debug and Trace classes, it took quite some time for the full implications of those dialogs to hit me. If that felt like lightning, the realization that followed felt like an earthquake.

Asserts in C# do not throw exceptions.

Yes, I know that when you turn off assertions for release builds in Delphi, you won’t get any exceptions either and your code is not protected from contract violations, but… they do do so in debug builds and they do do so for assertions that you “keep” in release builds even when turning off the large majority of your asserts.

So Trace.Assert not throwing an exception when the “condition is false” is a big deal. Because when the code continues to execute in a situation with arguments that it wasn’t designed to handle, that tends to produce the most horrific and hard to debug subtle errors.

Surprise recovery

Is there a way to recover from these surprise?

Yup! Of course, there is.

To get the behavior I expect from an Assert call, I just coded up two simple classes: DebugEx and TraceEx. They give me the best of both worlds. The ease of selecting whether the Assert makes it into a release build from C# with the exception throwing protection of code from Delphi.

    class AssertionFailure : Exception 
    {
        public AssertionFailure() : base() { }
        public AssertionFailure(string message) : base(message) { }
        //public AssertionFailure(SerializationInfo info, StreamingContext context) : base(info, context) { }
        public AssertionFailure(string message, Exception innerException) : base(message, innerException) { }
    }

    class DebugEx
    {
        [Conditional("DEBUG")]
        public static void Assert(bool condition)
        {
            if (!condition)
                throw new AssertionFailure("Assertion condition failed.");
        }
        
        [Conditional("DEBUG")]
        public static void Assert(Boolean condition, string message)
        {
            if (!condition)
                throw new AssertionFailure(message);
        }

        [Conditional("DEBUG")]
        public static void Assert(bool condition, string message, string detailMessage)
        {
            if (!condition)
                throw new AssertionFailure(string.Join("\n", message, detailMessage));
        }
        
        [Conditional("DEBUG")]
        public static void Assert(bool condition, string message, string detailMessageFormat, params object[] args)
        {
            if (!condition)
                throw new AssertionFailure(string.Join("\n", message, string.Format(detailMessageFormat, args)));
        }
    }

    class TraceEx
    {
        public static void Assert(bool condition)
        {
            if (!condition)
                throw new AssertionFailure("Assertion condition failed.");
        }

        public static void Assert(Boolean condition, string message)
        {
            if (!condition)
                throw new AssertionFailure(message);
        }

        public static void Assert(bool condition, string message, string detailMessage)
        {
            if (!condition)
                throw new AssertionFailure(string.Join("\n", message, detailMessage));
        }

        public static void Assert(bool condition, string message, string detailMessageFormat, params object[] args)
        {
            if (!condition)
                throw new AssertionFailure(string.Join("\n", message, string.Format(detailMessageFormat, args)));
        }
    }

That’s it. Enjoy!

What had you scratching your head when moving to C#? I’d love hearing from you. Just let me know in the comments below or by email.



2 comments on “4 surprises with asserts moving from Delphi to C#
  1. David Heffernan says:

    Apparently Debug.Assert the calls are automagically removed from assemblies compiled with the DEBUG conditional defined.

    You’ve got the logic switched around. The calls are removed if the DEBUG conditional is not defined.

    • Marjan Venema says:

      @David: you are absolutely right. Edited. Thanks for the catch!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Show Buttons
Hide Buttons