2

I have a preference of validating classes inside their constructors, but sometimes I'm a bit torn about doing this for certain low-level classes for performance concerns. When I start making 10's of thousands of these simple objects I often validate their inputs in higher-level logic, even though they also have constructor validation (logic typically fails-fast in methods, before I even get to the class c'tors).

I like to keep the low-level validation code as it helps debugging if I forget the higher-level catches. I also don't want to remove the low level validation code to make it faster because if I implement new methods that create these classes, things could fail silently or at an unexpected time.

So far, I have ignored performance concerns and always validated inside constructors using a similar pattern as demonstrated in the sample code below (see Validate.MustBeAbove<T> Extension method). I have added a simple and very naive approach to disabling the validation inside a using statement, but it obviously isn't threadsafe and it's still pretty slow (I assume the the method invocation is taking up most of the time??).

Questions:

  1. How can BarContext be implemented in a way that avoids thread-locks, but indicates that from c'tor to Dispose(), Validations should be skipped.?
  2. How can Profile_Case_2 come closer to the performance of Profile_Case_3?
  3. Should I just make "Unsafe" constructors for these low level types?

You should be able to copy-paste the code into a new console project and run it.

Results

Test Passed
Test Passed
Radius = 123.4
~~~~~~~~~~~~~~~~~
Ranked Results
~~~~~~~~~~~~~~~~~

Method: "Profile_Case_3_UnSafeFooObject_ConstructorAssignment"
Iterations = 100000, Warm Up Calls = 10
Results: Elapsed = 888ms, Average = 0.008875ms

Method: "Profile_Case_4_UnSafeFooObject_PublicFieldAssignment"
Iterations = 100000, Warm Up Calls = 10
Results: Elapsed = 924ms, Average = 0.009242ms

Method: "Profile_Case_2_FooObject_Without_Constructor_Validation"
Iterations = 100000, Warm Up Calls = 10
Results: Elapsed = 1,640ms, Average = 0.016399ms

Method: "Profile_Case_1__FooObject_With_Constructor_Validated"
Iterations = 100000, Warm Up Calls = 10
Results: Elapsed = 1,926ms, Average = 0.019259ms

Code

using FluentValidation;
using Foos;
using Performance;
using ProfileScenarios;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tests;

namespace OptimizeValidation
{
    class Program
    {
        // Constants
        const int WARM_UP_CALLS = 10;
        const int ITERATIONS = 100000;
        const int COUNT = 360;

        // Static for Debug
        static StringBuilder sb = new StringBuilder();

        // Main Entry Point
        static void Main(string[] args)
        {
            double Radius = 123.4;
            sb.AppendFormat("Radius = {0}\r\n", Radius);

            try
            {
                // Inline Test Cases
                Basic_Inline_Tests.FooObject_created_with_p_radius_of_negative_1_should_throw_ArgumentOutOfRangeException();
                Basic_Inline_Tests.FooObject_created_with_p_radius_of_negative_1_in_unchecked_mode_should_not_throw();
            }
            catch (Exception ex)
            {
                sb.AppendFormat("Unexpected Exception: {0}\r\n", ex.Message);
            }

            Profiler
                .Benchmark(
                    p_methodName: "Profile_Case_1__FooObject_With_Constructor_Validated",
                    p_warmUpCalls: WARM_UP_CALLS,
                    p_iterations: ITERATIONS,
                    p_action: () =>
                        {
                            List<IFooObject> ListOfFoos = ProfileCases.Profile_Case_1__FooObject_With_Constructor_Validated(Radius, COUNT);
                            ListOfFoos.Clear();
                        });

            Profiler
                .Benchmark(
                    p_methodName: "Profile_Case_2_FooObject_Without_Constructor_Validation",
                    p_warmUpCalls: WARM_UP_CALLS,
                    p_iterations: ITERATIONS,
                    p_action: () =>
                        {
                            List<IFooObject> ListOfFoos = ProfileCases.Profile_Case_2_FooObject_Without_Constructor_Validation(Radius, COUNT);
                            ListOfFoos.Clear();
                        });

            Profiler
                .Benchmark(
                    p_methodName: "Profile_Case_3_UnSafeFooObject_ConstructorAssignment",
                    p_warmUpCalls: WARM_UP_CALLS,
                    p_iterations: ITERATIONS,
                    p_action: () =>
                    {
                        List<IFooObject> ListOfFoos = ProfileCases.Profile_Case_3_UnSafeFooObject_ConstructorAssignment(Radius, COUNT);
                        ListOfFoos.Clear();
                    });

            Profiler
                .Benchmark(
                    p_methodName: "Profile_Case_4_UnSafeFooObject_PublicFieldAssignment",
                    p_warmUpCalls: WARM_UP_CALLS,
                    p_iterations: ITERATIONS,
                    p_action: () =>
                    {
                        List<IFooObject> ListOfFoos = ProfileCases.Profile_Case_4_UnSafeFooObject_PublicFieldAssignment(Radius, COUNT);
                        ListOfFoos.Clear();
                    });

            sb.AppendFormat("\r\n~~~~~~~~~~~~~~~~~\r\nRanked Results\r\n~~~~~~~~~~~~~~~~~\r\n{0}", Profiler.Ranked_Results(p_descending: false));

            Console.WriteLine(sb.ToString());
            Console.ReadKey();
        }
    }
}

namespace FluentValidation
{
    public static class Validate
    {
        // Static Fields
        public static bool m_disabled;

        /// <summary>
        /// Validates the passed in parameter is above a specified limit, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_limit">Limit to test parameter against.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <param name="p_variableValueName">Name of limit variable, if limit is not hard-coded.</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void MustBeAbove<T>(this IComparable<T> p_parameter, T p_limit, string p_name, string p_variableValueName = default(string))
            where T : struct
        {
            // Naive approach
            if (m_disabled)
                return;

            if (p_parameter.CompareTo(p_limit) <= 0)
                if (p_variableValueName != default(string))
                    throw
                        new
                            ArgumentOutOfRangeException(string.Format(
                            "Parameter must be greater than the value of \"{0}\" which was {1}, but \"{2}\" was {3}.",
                            p_variableValueName, p_limit, p_name, p_parameter), default(Exception));
                else
                    throw
                        new
                            ArgumentOutOfRangeException(string.Format(
                            "Parameter must be greater than {0}, but \"{1}\" was {2}.",
                            p_limit, p_name, p_parameter), default(Exception));
        }

        // Some Class to control whether validation actually happens
        public class BarContext : IDisposable
        {
            // What goes here to get the current Thread|Task so that a flag
            // can indicate that validation was performed at a higher level
            // and doesn't need to be repeated?
            public BarContext()
            {
                // Naive approach
                m_disabled = true;
            }

            // Dispose
            public void Dispose()
            {
                // Naive approach
                m_disabled = false;
            }
        }

        // Some method to return a thread specific context object
        public static IDisposable UncheckedContext()
        {
            // What goes here?
            return
                new BarContext();
        }
    }
}
namespace ProfileScenarios
{
    public static class ProfileCases
    {
        // Profile Scenarios
        public static List<IFooObject> Profile_Case_1__FooObject_With_Constructor_Validated(double p_radius, int p_count)
        {
            // Validate
            p_radius
                .MustBeAbove(0, "p_radius");
            p_count
                .MustBeAbove(0, "p_count");

            var FooList = new List<IFooObject>(p_count);

            for (int i = 0; i < p_count; i++)
                FooList.Add(new FooObject(p_radius, i));

            return
                FooList;
        }
        public static List<IFooObject> Profile_Case_2_FooObject_Without_Constructor_Validation(double p_radius, int p_count)
        {
            // Validate
            p_radius
                .MustBeAbove(0, "p_radius");
            p_count
                .MustBeAbove(0, "p_count");

            var FooList = new List<IFooObject>(p_count);

            using (var UncheckedMode = Validate.UncheckedContext())
            {
                for (int i = 0; i < p_count; i++)
                    FooList.Add(new FooObject(p_radius, i));
            }

            return
                FooList;
        }
        public static List<IFooObject> Profile_Case_3_UnSafeFooObject_ConstructorAssignment(double p_radius, int p_count)
        {
            // Validate
            p_radius
                .MustBeAbove(0, "p_radius");
            p_count
                .MustBeAbove(0, "p_count");

            var FooList = new List<IFooObject>(p_count);

            for (int i = 0; i < p_count; i++)
                FooList.Add(new UnSafeFooObject_ConstructorAssignment(p_radius, i));

            return
                FooList;
        }
        public static List<IFooObject> Profile_Case_4_UnSafeFooObject_PublicFieldAssignment(double p_radius, int p_count)
        {
            // Validate
            p_radius
                .MustBeAbove(0, "p_radius");
            p_count
                .MustBeAbove(0, "p_count");

            var FooList = new List<IFooObject>(p_count);

            for (int i = 0; i < p_count; i++)
            {
                var Foo = new UnSafeFooObject_PublicFieldAssignment();
                Foo.Radius = p_radius;
                Foo.Angle = i;
                FooList.Add(Foo);
            }

            return
                FooList;
        }
    }
}
namespace Tests
{
    public static class Basic_Inline_Tests
    {
        public static void FooObject_created_with_p_radius_of_negative_1_should_throw_ArgumentOutOfRangeException()
        {
            try
            {
                // Test
                new FooObject(-1, 123);

                // Test Failed
                throw
                    new InvalidOperationException(
                        "FooObject created with p_radius of -1 should throw ArgumentOutOfRangeException.");
            }
            catch (ArgumentOutOfRangeException)
            {
                // Test Passed
                Console.WriteLine("Test Passed");
            }
        }
        public static void FooObject_created_with_p_radius_of_negative_1_in_unchecked_mode_should_not_throw()
        {
            try
            {
                // Test
                using (var UncheckedMode = Validate.UncheckedContext())
                {
                    new FooObject(-1, 123);
                }
            }
            catch (ArgumentOutOfRangeException ex)
            {
                // Test Failed
                throw
                    new InvalidOperationException(
                        "FooObject created in Unchecked Mode threw an exception.", ex);
            }

            // Test Passed
            Console.WriteLine("Test Passed");
        }
    }
}
namespace Foos
{
    public interface IFooObject
    {
        /*Placeholder*/
    }

    public class FooObject : IFooObject
    {
        // Fields
        private double m_radius;
        private double m_angle;

        // Properties
        public double Radius { get { return m_radius; } }
        public double Angle { get { return m_angle; } }

        // Constructor
        public FooObject(double p_radius, double p_angle)
        {
            // Validate
            p_radius
                .MustBeAbove(0, "p_radius");

            // Init
            m_radius = p_radius;
            m_angle = p_angle;
        }
    }
    public class UnSafeFooObject_ConstructorAssignment : IFooObject
    {
        // Fields
        private double m_radius;
        private double m_angle;

        // Properties
        public double Radius { get { return m_radius; } }
        public double Angle { get { return m_angle; } }

        // Constructor
        public UnSafeFooObject_ConstructorAssignment(double p_radius, double p_angle)
        {
            //// Validate
            //p_radius
            //    .MustBeAboveZero("p_radius");

            // Init
            m_radius = p_radius;
            m_angle = p_angle;
        }
    }
    public class UnSafeFooObject_PublicFieldAssignment : IFooObject
    {
        // Public Fields
        public double Radius;
        public double Angle;
    }
}
namespace Performance
{
    public static class Profiler
    {
        // Fields
        private static List<ResultDetails> m_results = new List<ResultDetails>();
        private static readonly object m_syncObject = new object();

        // Properties
        public static string TimerInfo
        {
            get
            {
                return
                    string.Format("Timer: Frequency = {0:N0}Hz, Resolution = {1:N0}ns\r\n",
                    Stopwatch.Frequency,
                    ((1000L * 1000L * 1000L) / Stopwatch.Frequency));
            }
        }

        // Methods
        public static string Benchmark(string p_methodName, int p_iterations, int p_warmUpCalls, Action p_action)
        {
            lock (m_syncObject)
            {
                // Prepare Environment
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();

                // Warm Up
                for (int i = 0; i < p_warmUpCalls; i++)
                    p_action();

                // Profile
                var Timer = Stopwatch.StartNew();
                for (int i = 0; i < p_iterations; i++)
                    p_action();

                Timer.Stop();

                // Return Results
                var Details = new StringBuilder();
                var Ellapsed = Timer.Elapsed.TotalMilliseconds;
                var Average = (Timer.Elapsed.TotalMilliseconds / (double)p_iterations);

                Details
                    .AppendFormat("Method: \"{0}\"\r\n", p_methodName)
                    .AppendFormat("Iterations = {0:D}, Warm Up Calls = {1:D}\r\n", p_iterations, p_warmUpCalls)
                    .AppendFormat("Results: Elapsed = {0:N0}ms, Average = {1:N6}ms\r\n", Ellapsed, Average);

                m_results.Add(new ResultDetails(Average, Details.ToString()));

                return Details.ToString();
            }
        }
        public static string Ranked_Results(bool p_descending)
        {
            lock (m_syncObject)
            {
                var sb = new StringBuilder();
                var OrderedList = (p_descending) ? m_results.OrderByDescending(result => result.Average) : m_results.OrderBy(result => result.Average);

                foreach (var Result in OrderedList)
                    sb.AppendLine(Result.Details);

                return sb.ToString();
            }
        }
        public static void Clear()
        {
            lock (m_syncObject)
            {
                m_results.Clear();
            }
        }

        // Child Class
        private class ResultDetails
        {
            public double Average { get; private set; }
            public string Details { get; private set; }

            public ResultDetails(double p_average, string p_details)
            {
                Average = p_average;
                Details = p_details;
            }
        }
    }
}
leppie
  • 115,091
  • 17
  • 196
  • 297
HodlDwon
  • 1,131
  • 1
  • 13
  • 30

2 Answers2

1

It's a bit ugly, but after writing the profiler cases yesterday, I realized the method-invocation was probably the heaviest part of validation code (see the "naive" approach for case 2 in the original post... it had very minimal performance impact).

That got me thinking that the "ideal" was only ever going to be as fast as plain-old constructor or property assignment without any validation code... but I despise compiler directives for disabling code (I avoid them at all costs) and I hadn't yet thought of a good way to indicate to the next programmer the intention of the overloads (since the overloads themselves can't have unique names).

So I settled on a throw away class that is only used to indicate that an unvalidated constructor should be called instead of the validated one. It gets very, very, close to ideal level of performance (Note that my OP results were in debug mode with the debugger attached, because I forgot to set it to release. The results below are from a release build with no debugger attached, which makes all the methods much faster compared to the OP).

The answers to my original questions are:

How can BarContext be implemented in a way that avoids thread-locks, but indicates that from c'tor to Dispose(), Validations should be skipped?

A: You can set a ThreadStatic variable as a flag, but it requires cooperation between classes for best performance. Also in cases where a Task is spawned, the flag will not carry forward and would need to be set for each child Task individually.

How can Profile_Case_2 come closer to the performance of Profile_Case_3?

A: You have to avoid the invocation in the first place, it seems to be the heaviest part of the validation code. That said, even the evaluation of a ThreadStatic variable causes a ~30% performance hit compared to the ideal, which indicates that this is already working extremely quickly.

Should I just make "Unsafe" constructors for these low level types?

A: Yes, overload the class constructor to allow for unvalidated parameter assignment. You won't achieve any better performance than that based on testing. Using a dummy class with a clear name indicating it's purpose to force the overload also helps other programmers understand the intent of the code.

WARM_UP_CALLS = 10
ITERATIONS = 100000
COUNT = 360
Radius = 123.4
Test Passed: p_radius of -1 was invalid.
Test Passed: p_radius of -1 was ignored using unchecked mode.
Test Passed: p_radius of -1 was ignored by unsafe overload.
Test Passed: p_radius of -1 after dispose was invalid.

~~~~~~~~~~~~~~~~~
Ranked Results
~~~~~~~~~~~~~~~~~
Method: "Profile_Case_4_UnSafeFooObject_PublicFieldAssignment"
Iterations = 100000, Warm Up Calls = 10
Results: Elapsed = 666ms, Average = 0.006658ms, Efficiency = 100.00%

Method: "Profile_Case_5_FooObject_With_UnSafeOverload"
Iterations = 100000, Warm Up Calls = 10
Results: Elapsed = 670ms, Average = 0.006695ms, Efficiency = 99.44%

Method: "Profile_Case_3_UnSafeFooObject_ConstructorAssignment"
Iterations = 100000, Warm Up Calls = 10
Results: Elapsed = 674ms, Average = 0.006737ms, Efficiency = 98.83%

Method: "Profile_Case_2_FooObject_using_UnSafeFlag"
Iterations = 100000, Warm Up Calls = 10
Results: Elapsed = 898ms, Average = 0.008975ms, Efficiency = 74.18%

Method: "Profile_Case_1_FooObject_With_Constructor_Validation"
Iterations = 100000, Warm Up Calls = 10
Results: Elapsed = 1,081ms, Average = 0.010812ms, Efficiency = 61.58%


Press Escape to exit, or any other key to run again.
namespace FluentValidation
{
    public class ValidateOverload
    { }

    public static class Validate
    {
        // Static Fields
        public static ValidateOverload UnSafeOverload = new ValidateOverload();
        ...
    }
}

It breaks DRY principles a bit, but the intention is clear (I could call the Validated version as a : this() c'tor, but the code gets hard to follow because of the call order of the constructors... so I'd rather break the DRY principle for simple classes like these).

    // Constructor
    public FooObject(double p_radius, double p_angle)
    {
        // Validate
        p_radius
            .MustBeAbove(0, "p_radius");

        // Init
        m_radius = p_radius;
        m_angle = p_angle;
    }
    public FooObject(double p_radius, double p_angle, ValidateOverload p_uncheckedMode)
    {
        // Init
        m_radius = p_radius;
        m_angle = p_angle;
    }

Updated Code from OP, See Profile_Case_5

using FluentValidation;
using Foos;
using Performance;
using ProfileScenarios;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tests;

namespace OptimizeValidation
{
    class Program
    {
        // Constants
        const int WARM_UP_CALLS = 10;
        const int ITERATIONS = 100000;
        const int COUNT = 360;

        // Static for Debug
        static StringBuilder sb = new StringBuilder();

        // Main Entry Point
        static void Main(string[] args)
        {
            double Radius = 123.4;

            Console.WriteLine("WARM_UP_CALLS = {0}", WARM_UP_CALLS);
            Console.WriteLine("ITERATIONS = {0}", ITERATIONS);
            Console.WriteLine("COUNT = {0}", COUNT);
            Console.WriteLine("Radius = {0}", Radius);

            try
            {
                // Inline Test Cases
                Basic_Inline_Tests.FooObject_created_with_p_radius_of_negative_1_should_throw_ArgumentOutOfRangeException();
                Basic_Inline_Tests.FooObject_created_with_p_radius_of_negative_1_using_unchecked_mode_should_not_throw();
                Basic_Inline_Tests.FooObject_created_with_p_radius_of_negative_1_with_unsafe_overload_should_not_throw();
                Basic_Inline_Tests.FooObject_created_with_p_radius_of_negative_1_using_unchecked_mode_should_throw_after_dispose();
            }
            catch (Exception ex)
            {
                sb.AppendFormat("Unexpected Exception: {0}\r\n", ex.Message);
            }

            do
            {

                Profiler
                    .Benchmark(
                        p_methodName: "Profile_Case_1_FooObject_With_Constructor_Validation",
                        p_warmUpCalls: WARM_UP_CALLS,
                        p_iterations: ITERATIONS,
                        p_action: () =>
                            {
                                List<IFooObject> ListOfFoos = ProfileCases.Profile_Case_1_FooObject_With_Constructor_Validation(Radius, COUNT);
                                ListOfFoos.Clear();
                            });

                Profiler
                    .Benchmark(
                        p_methodName: "Profile_Case_2_FooObject_using_UnSafeFlag",
                        p_warmUpCalls: WARM_UP_CALLS,
                        p_iterations: ITERATIONS,
                        p_action: () =>
                            {
                                List<IFooObject> ListOfFoos = ProfileCases.Profile_Case_2_FooObject_using_UnSafeFlag(Radius, COUNT);
                                ListOfFoos.Clear();
                            });

                Profiler
                    .Benchmark(
                        p_methodName: "Profile_Case_3_UnSafeFooObject_ConstructorAssignment",
                        p_warmUpCalls: WARM_UP_CALLS,
                        p_iterations: ITERATIONS,
                        p_action: () =>
                        {
                            List<IFooObject> ListOfFoos = ProfileCases.Profile_Case_3_UnSafeFooObject_ConstructorAssignment(Radius, COUNT);
                            ListOfFoos.Clear();
                        });

                Profiler
                    .Benchmark(
                        p_methodName: "Profile_Case_4_UnSafeFooObject_PublicFieldAssignment",
                        p_warmUpCalls: WARM_UP_CALLS,
                        p_iterations: ITERATIONS,
                        p_action: () =>
                        {
                            List<IFooObject> ListOfFoos = ProfileCases.Profile_Case_4_UnSafeFooObject_PublicFieldAssignment(Radius, COUNT);
                            ListOfFoos.Clear();
                        });

                Profiler
                    .Benchmark(
                        p_methodName: "Profile_Case_5_FooObject_With_UnSafeOverload",
                        p_warmUpCalls: WARM_UP_CALLS,
                        p_iterations: ITERATIONS,
                        p_action: () =>
                        {
                            List<IFooObject> ListOfFoos = ProfileCases.Profile_Case_5_FooObject_With_UnSafeOverload(Radius, COUNT);
                            ListOfFoos.Clear();
                        });

                sb
                    .AppendFormat(
                        "\r\n~~~~~~~~~~~~~~~~~\r\nRanked Results\r\n~~~~~~~~~~~~~~~~~\r\n{0}",
                        Profiler.Ranked_Results(p_descending: false));

                Console.WriteLine(sb.ToString());
                Console.WriteLine("Press Escape to exit, or any other key to run again.");

                // Reset
                Profiler.Clear();
                sb.Clear();
            }
            while (Console.ReadKey().Key != ConsoleKey.Escape);
        }
    }
}

namespace FluentValidation
{
    public class ValidateOverload
    {
    }

    public static class Validate
    {
        // ThreadStatic see example code http://msdn.microsoft.com/en-us/library/system.threading.thread.setdata%28v=vs.110%29.aspx

        // Static Fields
        [ThreadStatic]
        public static bool UncheckedMode;
        public static ValidateOverload UnSafeOverload = new ValidateOverload();

        /// <summary>
        /// Validates the passed in parameter is above a specified limit, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_limit">Limit to test parameter against.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <param name="p_variableValueName">Name of limit variable, if limit is not hard-coded.</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void MustBeAbove<T>(this IComparable<T> p_parameter, T p_limit, string p_name, string p_variableValueName = default(string))
            where T : struct
        {
            if (p_parameter.CompareTo(p_limit) <= 0)
                if (p_variableValueName != default(string))
                    throw
                        new
                            ArgumentOutOfRangeException(string.Format(
                            "Parameter must be greater than the value of \"{0}\" which was {1}, but \"{2}\" was {3}.",
                            p_variableValueName, p_limit, p_name, p_parameter), default(Exception));
                else
                    throw
                        new
                            ArgumentOutOfRangeException(string.Format(
                            "Parameter must be greater than {0}, but \"{1}\" was {2}.",
                            p_limit, p_name, p_parameter), default(Exception));
        }

        // Method
        internal static IDisposable UncheckedThreadContext()
        {
            return
                new UnCheckedContext();
        }

        private class UnCheckedContext : IDisposable
        {
            public UnCheckedContext()
            {
                UncheckedMode = true;
            }

            // Dispose
            public void Dispose()
            {
                UncheckedMode = false;
            }
        }
    }
}
namespace ProfileScenarios
{
    public static class ProfileCases
    {
        // Profile Scenarios
        public static List<IFooObject> Profile_Case_1_FooObject_With_Constructor_Validation(double p_radius, int p_count)
        {
            // Validate
            p_radius
                .MustBeAbove(0, "p_radius");
            p_count
                .MustBeAbove(0, "p_count");

            var FooList = new List<IFooObject>(p_count);

            for (int i = 0; i < p_count; i++)
                FooList.Add(new FooObject(p_radius, i));

            return
                FooList;
        }
        public static List<IFooObject> Profile_Case_2_FooObject_using_UnSafeFlag(double p_radius, int p_count)
        {
            // Validate
            p_radius
                .MustBeAbove(0, "p_radius");
            p_count
                .MustBeAbove(0, "p_count");

            var FooList = new List<IFooObject>(p_count);

            using (Validate.UncheckedThreadContext())
            {
                for (int i = 0; i < p_count; i++)
                    FooList.Add(new FooObject_IfUnChecked(p_radius, i));
            }

            return
                FooList;
        }
        public static List<IFooObject> Profile_Case_3_UnSafeFooObject_ConstructorAssignment(double p_radius, int p_count)
        {
            // Validate
            p_radius
                .MustBeAbove(0, "p_radius");
            p_count
                .MustBeAbove(0, "p_count");

            var FooList = new List<IFooObject>(p_count);

            for (int i = 0; i < p_count; i++)
                FooList.Add(new UnSafeFooObject_ConstructorAssignment(p_radius, i));

            return
                FooList;
        }
        public static List<IFooObject> Profile_Case_4_UnSafeFooObject_PublicFieldAssignment(double p_radius, int p_count)
        {
            // Validate
            p_radius
                .MustBeAbove(0, "p_radius");
            p_count
                .MustBeAbove(0, "p_count");

            var FooList = new List<IFooObject>(p_count);

            for (int i = 0; i < p_count; i++)
            {
                var Foo = new UnSafeFooObject_PublicFieldAssignment();
                Foo.Radius = p_radius;
                Foo.Angle = i;
                FooList.Add(Foo);
            }

            return
                FooList;
        }
        public static List<IFooObject> Profile_Case_5_FooObject_With_UnSafeOverload(double p_radius, int p_count)
        {
            // Validate
            p_radius
                .MustBeAbove(0, "p_radius");
            p_count
                .MustBeAbove(0, "p_count");

            var FooList = new List<IFooObject>(p_count);

            for (int i = 0; i < p_count; i++)
                FooList.Add(new FooObject(p_radius, i, Validate.UnSafeOverload));

            return
                FooList;
        }
    }
}
namespace Tests
{
    public static class Basic_Inline_Tests
    {
        public static void FooObject_created_with_p_radius_of_negative_1_should_throw_ArgumentOutOfRangeException()
        {
            try
            {
                // Test
                new FooObject(-1, 123);

                // Test Failed
                throw
                    new InvalidOperationException(
                        "FooObject created with p_radius of -1 should throw ArgumentOutOfRangeException.");
            }
            catch (ArgumentOutOfRangeException)
            {
                // Test Passed
                Console.WriteLine("Test Passed: p_radius of -1 was invalid.");
            }
        }
        public static void FooObject_created_with_p_radius_of_negative_1_using_unchecked_mode_should_not_throw()
        {
            try
            {
                // Test
                using (Validate.UncheckedThreadContext())
                {
                    new FooObject_IfUnChecked(-1, 123);
                }
            }
            catch (ArgumentOutOfRangeException ex)
            {
                // Test Failed
                throw
                    new InvalidOperationException(
                        "FooObject created using unchecked mode threw an unexpected exception.", ex);
            }

            // Test Passed
            Console.WriteLine("Test Passed: p_radius of -1 was ignored using unchecked mode.");
        }
        public static void FooObject_created_with_p_radius_of_negative_1_using_unchecked_mode_should_throw_after_dispose()
        {
            try
            {
                // Test
                using (Validate.UncheckedThreadContext())
                {
                    new FooObject_IfUnChecked(-1, 123);
                }

                new FooObject_IfUnChecked(-1, 123);

                // Test Failed
                throw
                    new InvalidOperationException(
                        "FooObject created with p_radius of -1 after dispose should throw ArgumentOutOfRangeException.");
            }
            catch (ArgumentOutOfRangeException)
            {
                // Test Passed
                Console.WriteLine("Test Passed: p_radius of -1 after dispose was invalid.");
            }
        }
        public static void FooObject_created_with_p_radius_of_negative_1_with_unsafe_overload_should_not_throw()
        {
            try
            {
                // Test
                new FooObject(-1, 123, Validate.UnSafeOverload);
            }
            catch (ArgumentOutOfRangeException ex)
            {
                // Test Failed
                throw
                    new InvalidOperationException(
                        "FooObject created with unsafe overload threw an unexpected exception.", ex);
            }

            // Test Passed
            Console.WriteLine("Test Passed: p_radius of -1 was ignored by unsafe overload.");
        }
    }
}
namespace Foos
{
    public interface IFooObject
    {
        /*Placeholder*/
    }

    public class FooObject : IFooObject
    {
        // Fields
        private double m_radius;
        private double m_angle;

        // Properties
        public double Radius { get { return m_radius; } }
        public double Angle { get { return m_angle; } }

        // Constructor
        public FooObject(double p_radius, double p_angle)
        {
            // Validate
            p_radius
                .MustBeAbove(0, "p_radius");

            // Init
            m_radius = p_radius;
            m_angle = p_angle;
        }
        public FooObject(double p_radius, double p_angle, ValidateOverload p_uncheckedMode)
        {
            // Init
            m_radius = p_radius;
            m_angle = p_angle;
        }
    }
    public class FooObject_IfUnChecked : IFooObject
    {
        // Fields
        private double m_radius;
        private double m_angle;

        // Properties
        public double Radius { get { return m_radius; } }
        public double Angle { get { return m_angle; } }

        // Constructor
        public FooObject_IfUnChecked(double p_radius, double p_angle)
        {
            // Validate
            if (!Validate.UncheckedMode)
            {
                p_radius
                    .MustBeAbove(0, "p_radius");
            }

            // Init
            m_radius = p_radius;
            m_angle = p_angle;
        }
    }
    public class UnSafeFooObject_ConstructorAssignment : IFooObject
    {
        // Fields
        private double m_radius;
        private double m_angle;

        // Properties
        public double Radius { get { return m_radius; } }
        public double Angle { get { return m_angle; } }

        // Constructor
        public UnSafeFooObject_ConstructorAssignment(double p_radius, double p_angle)
        {
            // Init
            m_radius = p_radius;
            m_angle = p_angle;
        }
    }
    public class UnSafeFooObject_PublicFieldAssignment : IFooObject
    {
        // Public Fields
        public double Radius;
        public double Angle;
    }
}
namespace Performance
{
    public static class Profiler
    {
        // Fields
        private static List<ResultDetails> m_results = new List<ResultDetails>();
        private static readonly object m_syncObject = new object();

        // Properties
        public static string TimerInfo
        {
            get
            {
                return
                    string.Format("Timer: Frequency = {0:N0}Hz, Resolution = {1:N0}ns\r\n",
                    Stopwatch.Frequency,
                    ((1000L * 1000L * 1000L) / Stopwatch.Frequency));
            }
        }

        // Methods
        public static string Benchmark(string p_methodName, int p_iterations, int p_warmUpCalls, Action p_action)
        {
            // Profiler example http://stackoverflow.com/a/1048708/1718702

            lock (m_syncObject)
            {
                // Prepare Environment
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();

                // Warm Up
                for (int i = 0; i < p_warmUpCalls; i++)
                    p_action();

                // Profile
                var MethodTimer = Stopwatch.StartNew();
                for (int i = 0; i < p_iterations; i++)
                    p_action();

                MethodTimer.Stop();

                // Return Results
                var Details = new StringBuilder();
                var Ellapsed = MethodTimer.Elapsed.TotalMilliseconds;
                var Average = (MethodTimer.Elapsed.TotalMilliseconds / (double)p_iterations);

                Details
                    .AppendFormat("Method: \"{0}\"\r\n", p_methodName)
                    .AppendFormat("Iterations = {0:D}, Warm Up Calls = {1:D}\r\n", p_iterations, p_warmUpCalls)
                    .AppendFormat("Results: Elapsed = {0:N0}ms, Average = {1:N6}ms\r\n", Ellapsed, Average);

                m_results.Add(new ResultDetails(Average, Details.ToString()));

                return Details.ToString();
            }
        }
        public static string Ranked_Results(bool p_descending)
        {
            lock (m_syncObject)
            {
                var sb = new StringBuilder();
                var OrderedList = (p_descending) ? m_results.OrderByDescending(result => result.Average) : m_results.OrderBy(result => result.Average);

                double FastestImplementation = OrderedList.Select(r => r.Average).Min();
                foreach (var Result in OrderedList)
                {
                    sb
                        .Append(Result.Details.TrimEnd())
                        .AppendFormat(", Efficiency = {0:N2}%", (FastestImplementation / Result.Average) * 100)
                        .AppendLine()
                        .AppendLine();
                }

                return sb.ToString();
            }
        }
        public static void Clear()
        {
            lock (m_syncObject)
            {
                m_results.Clear();
            }
        }

        // Child Class
        private class ResultDetails
        {
            public double Average { get; private set; }
            public string Details { get; private set; }

            public ResultDetails(double p_average, string p_details)
            {
                Average = p_average;
                Details = p_details;
            }
        }
    }
}
HodlDwon
  • 1,131
  • 1
  • 13
  • 30
0

Would you not just want to create a compiler constant ("DEBUGCHECKED") that you use to conditionally compile the validation code ? Your release version (having been thoroughly tested) could omit the directive and gain the performance benefit, but in development (where you'd expect the mistakes to be made) you would use it to opt-in the validation code.

PhillipH
  • 6,182
  • 1
  • 15
  • 25