Duraxium.Diagnostics 2.0.0

dotnet add package Duraxium.Diagnostics --version 2.0.0
NuGet\Install-Package Duraxium.Diagnostics -Version 2.0.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Duraxium.Diagnostics" Version="2.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Duraxium.Diagnostics --version 2.0.0
#r "nuget: Duraxium.Diagnostics, 2.0.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Duraxium.Diagnostics as a Cake Addin
#addin nuget:?package=Duraxium.Diagnostics&version=2.0.0

// Install Duraxium.Diagnostics as a Cake Tool
#tool nuget:?package=Duraxium.Diagnostics&version=2.0.0

Duraxium Diagnostic Library

Version Date Notes
1.0.0 31 Jan 2021 Origination. The debug dialog currently supports English, Czech, German, Spanish, French, Italian, Dutch, Portuguese, and Swedish.
1.1.0 5 Sep 2021 Addresses a potential problem with config file location.
1.2.0 19 Sep 2021 Abandon config file entirely - not feasible for read-only application folders. An application can use its own config file to set the Audit class options if desired.
2.0.0 4 Feb 2024 Add the 'Ensure' class, used to write parameter guard conditions.

The Duraxium diagnostic library provides two different diagnostic aids –

  • A class with methods that provide syntactically concise "guard conditions" for checking parameter values in your code.
  • A class that provides assertion-checking similar to the .NET Debug.Assert method, but with extra features.

The Ensure class is used for checking parameter values, and it throws an appropriate exception when a value is invalid or out of range.

The Audit class is used to add assertions, helping to ensure program correctness based on design-by-contract principles.

The Audit.Verify method works much like Debug.Assert in that it checks a condition that you specify. If the condition is false, the Verify produces an “alert” – throwing an exception or displaying a debug dialog. The choice of an exception or dialog is configurable. The debug dialog supports nine languages – Czech, Dutch, English, French, German, Italian, Portuguese, Spanish, and Swedish.

As a rule, exception-throwing guard conditions (as with Ensure) are preferred in a library used by others outside of your organization, while assertions (as with Audit) are preferred in code that is not used programmatically outside of your organization.

Ensure Class Reference

The Ensure class lets you check parameter values at run-time, throwing an exception if the value is not valid. Though it's quite easy to add guard conditions without a helper class, the Ensure class makes the statements more concise syntactically, and therefore easier to read and understand.

public static bool IsActive { get; set; }

Enable or disable the Ensure methods. No exceptions will be thrown if IsActive is false. By default, IsActive is true.

public static void IsValid(bool condition, string paramName = null)

Check whether a specific condition is true. An DuraxiumArgumentException will be thrown if the condition is not true.

condition – the condition to check

paramName – the parameter name (optional)

public static void IsNotNull(object val, string paramName = null)

Check that a parameter value is not null. A DuraxiumArgumentNullException will be thrown if the value is null.

val – the parameter value to check

paramName – the parameter name (optional)

public static void IsNotNullEmptyWhitespace(string val, string paramName = null)

Check that a string parameter value is not null, empty, or whitespace. A DuraxiumArgumentNullException will be thrown if the value is null. A DuraxiumArgumentWhitespaceException will be thrown if the value is empty or whitespace.

val – the parameter value to check

paramName – the parameter name (optional)

public static void IsLessThan<T>(T val, T max, string paramName = null)

Check that a parameter value is less than a specific value. An DuraxiumArgumentOutOfRangeException will be thrown if the value is not less than the specified value.

val – the parameter value to check

max – the maximum allowed value (exclusive)

paramName – the parameter name (optional)

public static void IsLessThanOrEqual<T>(T val, T max, string paramName = null)

Check that a parameter value is less than or equal to a specific value. An DuraxiumArgumentOutOfRangeException will be thrown if the value is not less than or equal to the specified value.

val – the parameter value to check

max – the maximum allowed value (inclusive)

paramName – the parameter name (optional)

public static void IsGreaterThan<T>(T val, T min, string paramName = null)

Check that a parameter value is greater than a specific value. An DuraxiumArgumentOutOfRangeException will be thrown if the value is not greater than the specified value.

val – the parameter value to check

min – the minimum allowed value (exclusive)

paramName – the parameter name (optional)

public static void IsGreaterThanOrEqual<T>(T val, T min, string paramName = null)

Check that a parameter value is greater than or equal to a specific value. An DuraxiumArgumentOutOfRangeException will be thrown if the value is not greater than or equal to the specified value.

val – the parameter value to check

min – the minimum allowed value (inclusive)

paramName – the parameter name (optional)

public static void IsBetween<T>(T val, T min, T max, string paramName = null)

Check that a parameter value is between a specified minimum and maximum value. An DuraxiumArgumentOutOfRangeException will be thrown if the value is not between the specified values.

val – the parameter value to check

min – the minimum allowed value (exclusive)

max – the maximum allowed value (exclusive)

paramName – the parameter name (optional)

C# Ensure Example

Here's an example showing how guard conditions might be implemented traditionally –

public string MyUsefulMethod(Random rand, string prefix, double cost)
{
    if (rand == null) {
        throw new ArgumentNullException('rand');
    }
    
    if (string.IsNullOrWhiteSpace(prefix)) {
        throw new ArgumentException('prefix');
    }
    
    if (cost <= 0.0) {
        throw new ArgumentOutOfRangeException('cost');
    }
    
    ...
}

A bit messy if you have more than one or two parameters. Here's an example showing how to use the Ensure class methods instead –

public string MyUsefulMethod(Random rand, string prefix, double cost)
{
	Ensure.IsNotNull(rand, nameof(rand));
	Ensure.IsNotNullEmptyWhitespace(prefix, nameof(prefix));
	Ensure.IsGreaterThan(cost, 0.0, nameof(cost));

    ...
}
Ensure Notes

The Ensure methods that check the range of a value usually involve numeric types like int or double. However, any object that implements the IComparable interface can be used. Bear in mind that non-numeric types may be compared differently than you assume.

The parameter name string in the Ensure methods is optional, though helpful if you want more descriptive exception error messages. Using the C# nameof expression helps prevent incorrect messages if the parameter name is ever changed. The exception error messages are always in English.

Each of the Ensure class methods throws an exception whenever a condition fails.

A .NET exception object always has a stack trace showing the method call sequence at the point where the exception is thrown. Because exceptions are thrown by the Ensure class methods, the stack trace would normally begin with that method instead of the method in your class where your guard condition failed.

To avoid this behavior, the Ensure class methods throw custom exceptions. The stack trace of the custom exception is adjusted to start with your method instead of the Ensure class method. The custom exception classes are:

  • public class DuraxiumArgumentException : ArgumentException
  • public class DuraxiumOutOfRangeException : ArgumentOutOfRangeException
  • public class DuraxiumArgumentNullException : ArgumentNullException
  • public class DuraxiumArgumentWhitespaceException : ArgumentException

Because the custom exceptions are derived from standard .NET exception types, a catch expression can specify either the custom type or the underlying standard type.

Audit Class Reference

The Audit class lets you check a variety of program conditions at run-time, including preconditions and postconditions:

public static bool UseDialogBeep { get; set; }

Get or set the use of a sound when the debug dialog appears. UseDialogBeep is true by default.

public static bool NeverUseDialog { get; set; }

Get or set the use of the debug dialog. NeverUseDialog is false by default.

public static bool IsActive { get; set; }

Get or set whether the Verify method is active ever. IsActive is true by default.

public static bool IsActiveInReleaseBuild { get; set; }

Get or set whether the Verify method is active in a Release build. IsActiveInReleaseBuild is false by default.

public static void Verify(bool condition, string message = "")

Check a condition. If the condition evaluates to false, a debug dialog is displayed or an AuditException is thrown.

condition – the condition to monitor

message – the optional error message

**public static void AddAlertCallback(**AuditDelegate cb)

Add an alert callback handler.

cb – the callback to add

public static void ClearAlertCallbacks()

Clear all the alert callback handlers.

C# Verify Example

One use of the Verify method is to check method preconditions and postconditions, as shown in this example:

using System;
using Duraxium.Diagnostics;

namespace Drafting
{
  public class Circle : Shape
  {
    public Circle(Shape parent, string label, float radius)
    {
      // Three preconditions
      Audit.Verify(parent != null, “Parent may not be null”);
      Audit.Verify(string.IsNullOrWhitespace(label));
      Audit.Verify(radius > 0);

      Parent = parent;
      Label = label;
      Radius = radius;
      Area = (float)Math.PI * radius * radius;
      Circumference = (float)Math.PI * 2 * radius;

      // Two postconditions
      Audit.Verify(Area > 0, “Area must be greater than zero”);
      Audit.Verify(Circumference > radius);
    }

    public Shape Parent { get; private set; } 
    public string Label { get; private set; } 
    public float Radius { get; private set; } 
    public float Circumference { get; private set; } 
    public float Area { get; private set; }
  }
}

Adding Callbacks

When an alert occurs, you can perform additional actions by implementing a callback method with the signature of the AuditDelegate delegate:

​ public delegate void AuditDelegate(AlertDetails details);

The following callback logs alert details using the log4net library:

using Duraxium.Diagnostics;
using log4net;
using System;

namespace MyApplication
{
  /// <summary>
  /// Represents extended functionality for the Audit class.
  /// </summary>
  public class AuditExtension
  {
    private readonly ILog logger; 

    /// <summary>
    /// AuditExtension constructor.
    /// </summary>
    /// <param name="log">the log4net logging interface</param>
    public AuditExtension(ILog log)
    {
      logger = log;
    } 

    /// <summary>
    /// Log the alert details.
    /// </summary>
    /// <param name="details">the alert details</param>
    /// <remarks>Signature is the same as AuditDelegate.
    public void Log(AlertDetails details)
    {
      logger.Error(string.Format("Verify failure: {0}", details));
    }
  }
}

After creating the callback, you can use the Audit.AddAlertCallback method to add/install it. The callback should be added during application startup as shown below:

using Duraxium.Diagnostics;
using log4net;
using System;

namespace MyApplication
{
  class Program
  {
    private static readonly ILog logger = LogManager.GetLogger("AppLog");
    
    static void Main(string[] args)
    {
      log4net.Config.XmlConfigurator.Configure();
      logger.Info("Start logging..."); 

      AuditExtension ext = new AuditExtension(logger);
      Audit.AddAlertCallback(ext.Log); 
      . . .
    }
  }
}

All alert operations, including callbacks, are thread-safe and executed synchronously to ensure that alerts cannot overlap.

Unit Test Features

The only exception type thrown by an alert is AuditException, which makes exception handling simpler. The AuditException also lets you check your Verify conditions in your unit tests.

When your code is executed by a unit test, an alert will throw an AuditException instead of displaying the debug dialog. You can use ExpectedException attribute to check this:

[TestMethod]
[ExpectedException(typeof(AuditException))]
public void ConstructorTest()
{
  Circle c = new Circle(null, “target”, 3.4f);
}

The ExpectedException attribute will only detect the first alert triggered by your test method. To validate multiple conditions, it is better to use Assert.ThrowsException as shown here:

[TestMethod]
public void AltConstructorTest()
{
  Shape s = new Shape(); 

  Assert.ThrowsException<AuditException>(() => new Circle(null, “target”, 3.4f));
  Assert.ThrowsException<AuditException>(() => new Circle(s, null, 3.4f));
  Assert.ThrowsException<AuditException>(() => new Circle(s, “”, 3.4f));
  Assert.ThrowsException<AuditException>(() => new Circle(s, “buoy”, -0.0001f));
} 

If you prefer not to validate Verify statements in your unit tests, you should set the Audit.IsActive property to false during test initialization to avoid accidental alerts.

Audit Class Options

The Audit class has four properties that affect the behavior of the Verify method:

UseDialogBeep – Enable or disable a sound (beep) when the debug dialog is displayed. UseDialogBeep is true by default.

NeverUseDialog – Enable or disable the use of the debug dialog. If the dialog is disabled, only an AuditException will be thrown if an assertion fails. NeverUseDialog is false by default.

IsActive – Completely enable or disable the Verify method. IsActive is true by default.

IsActiveInReleaseBuild – Enable or disable assertion checking in a Release build. IsActiveInReleaseBuild is false by default.

Setting NeverUseDialog to true can be disruptive unless you catch the AuditException thrown by an alert. A convenient way to catch any AuditException is to add a last chance exception handler using the AppDomain.CurrentDomain.UnhandledException event.

By default, .NET code is optimized in a Release build but not a Debug build. Most people do not want alerts to occur in Release builds, so the Verify method is automatically disabled when your program uses optimized code. If you prefer to make alerts active in the Release build, you can set the IsActiveInReleaseBuild property to true.

Using a Wrapper

Although the Verify method is normally inactive in a Release build, the Verify calls are not compiled out. The slight performance penalty should not affect most applications, but if this is a concern, you can create a wrapper for the Audit class so it does get compiled out.

To work correctly, the wrapper needs to use the attributes shown in this example:

using Duraxium.Diagnostics;
using System.Diagnostics;

namespace MyApplication
{
  [AuditWrapper]
  public static class MyAudit
  {
    [Conditional("DEBUG")]
    [DebuggerHidden]
    public static void Verify(bool condition, string message = "")
    {
      Audit.Verify(condition, message);
    }
  }
}

The AuditWrapper attribute is needed to adjust the displayed call stack. The DebuggerHidden attribute is needed to stop the debugger on the correct statement when the Debug button is clicked. The Conditional attribute ensures that the MyAudit.Verify method is compiled in only in the Debug build.

AuditException Reference

An AuditException object may be thrown when an alert occurs:

**public AuditException(**AlertDetails details)

AuditException constructor.

details – the alert details

public string StackTrace { get; }

Get the current stack trace.

public string ToString()

Returns a string representation of the audit exception. This string is the same as the StackTrace string.

When an alert occurs, instead of displaying the debug dialog, an AuditException will be thrown when any of the following conditions are true:

  • The session is not interactive.

  • The code is invoked by a unit test.

  • The NeverUseDialog property is true.

AuditDetails Reference

The details about an assertion failure.

public string Message { get; }

Get the error message.

public List<string> StackInfo { get; }

Get the call stack information as a list of strings.

public override string ToString()

Get a string representation of the alert details.

Product Compatible and additional computed target framework versions.
.NET Framework net20 is compatible.  net35 is compatible.  net40 is compatible.  net403 was computed.  net45 is compatible.  net451 was computed.  net452 was computed.  net46 was computed.  net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETFramework 2.0

    • No dependencies.
  • .NETFramework 3.5

    • No dependencies.
  • .NETFramework 4.0

    • No dependencies.
  • .NETFramework 4.5

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.0.0 93 2/4/2024
1.2.0 336 9/19/2021
1.1.0 292 9/5/2021
1.0.0 324 1/31/2021

Added the 'Ensure' class, used to write parameter guard conditions.