Print

Extending CuttingEdge.Conditions

In a previous post I introduced CuttingEdge.Conditions, a library that helps developers to write pre- and postcondition validations in their .NET 3.5 code base. In this post I'll explain how you can extend the library.

Download: The CuttingEdge.Conditions library and source code can be downloaded from CodePlex.com. Visit the homepage at conditions.codeplex.com or go directly to the releases tab.

[UPDATE 2009-09-07]: This article is based on a beta release of CuttingEdge.Conditions. The library has changed considerably since, and this article should be considered outdated. Please visit the Extending CuttingEdge.Conditions wiki page on CodePlex for guidance on extending the current release.

Why would you want to extend the CuttingEdge.Conditions library?

While I believe the library covers most basic validation needs, developers will always have specific needs that CuttingEdge.Conditions (or any other library) doesn't cover. Though it is still possible to write those old fashion 'if (b) throw ex' statements, a more convenient solution is to write your own validation methods. To do this, there are two things you should pay attention to:

1. Place your extension class in your companies root namespace.
This way you'll never have to include that namespace. The only namespace you'll have to add is the CuttingEdge.Conditions namespace. After including this namespace, your own extension methods will show up automatically in the IntelliSense dropdown list.

2. Some members won't show up using IntelliSense.
Some members of the Validator<T> class are decorated with the EditorBrowsableAttribute, preventing them from showing up during IntelliSense. The most important hidden members are the ArgumentName and Value fields and the Throw method. Hiding those members makes a lot of sense, because they aren't used directly by users of the library. Showing them would clutter the API and make using the library more difficult. Hiding them however, makes extending the library harder and it is something you'll have to deal with while extending the library.

Examples

CuttingEdge.Conditions has several methods that allow you to check collections. The Contains method for instance, checks whether the given collection argument holds a certain value. The current beta 1 release however, lacks a check that works the other way around. That is: To check whether an argument value exists in a certain collection.

The following code snippet shows a possible implementation, that can be added to your code base.

using System;
using System.Collections.Generic;
using System.Linq;

using CuttingEdge.Conditions;

namespace YourCompanyRootNamespace
{
public static class CuttingEdgeConditionExtensions
{
public static Validator<T> ExistsIn<T>(
this Validator<T> validator, IEnumerable<T> collection)
{
if (collection == null ||
!collection.Contains(validator.Value))
{
// Throw and ArgumentName won't show up in the
// IntelliSense dropdown list.
validator.Throw(validator.ArgumentName +
" should be in the supplied collection");
}

return validator;
}
}
}

Another way of extending the library is by adding method overloads for the Requires and Ensures entry point methods. Judah Himango started a discussion in the message panel on the CuttingEdge.Conditions’ article on The Code Project. He proposed using lambda expressions instead of strings to determine the name of an argument. I decided not to add such a feature to CuttingEdge.Conditions, because of the performance implications that come with it. Not including this feature, isn’t actually a big deal, because it’s easy to add this feature to your own code base. Below is a code snippet that enables this feature.

using System;
using System.Diagnostics;
using System.Linq.Expressions;

using CuttingEdge.Conditions;

namespace YourCompanyRootNamespace
{
public static class ValidatorExpressionExtensions
{
private enum EntryPointType
{
Requires = 0,
Ensures,
}

[DebuggerStepThrough]
public static Validator<T> Requires<T>(this T value,
Expression<Func<T>> argNameExpr)
{
string argumentName = GetArgumentName(argNameExpr,
EntryPointType.Requires);

return ValidatorExtensions.Requires(value, argumentName);
}

[DebuggerStepThrough]
public static Validator<T> Ensures<T>(this T value,
Expression<Func<T>> argNameExpr)
{
string argumentName = GetArgumentName(argNameExpr,
EntryPointType.Ensures);

return ValidatorExtensions.Ensures(value, argumentName);
}

[DebuggerStepThrough]
public static Validator<T> Ensures<T>(this T value,
Expression<Func<T>> argNameExpr, string message)
{
string argumentName = GetArgumentName(argNameExpr,
EntryPointType.Ensures);

return ValidatorExtensions.Ensures(value, argumentName,
message);
}

const string ExceptionMessage =
"The specified expression '{0}' is a {1}, which is not " +
"supported. Only MemberExpressions are supported. Try " +
"expressing the statement as: x.{2}(() => x);";

[DebuggerStepThrough]
private static string GetArgumentName<T>(
Expression<Func<T>> argNameExpr, EntryPointType type)
{
if (argNameExpr == null)
{
throw new ArgumentNullException(
"argumentNameExpression");
}

MemberExpression memberExpression =
argNameExpr.Body as MemberExpression;

if (memberExpression == null)
{
throw new ArgumentException(
String.Format(ExceptionMessage,
argNameExpr.Body.ToString(),
argNameExpr.Body.GetType().Name,
type.ToString()),
"argumentNameExpression");
}

if (memberExpression.Member == null)
{
throw new ArgumentException(
"The body of the given expression lacks a Member.",
"argumentNameExpression");
}

return memberExpression.Member.Name;
}
}
}

You’ll see that extending CuttingEdge.Conditions is rather straight forward.

Happy coding.

- .NET General, C#, CuttingEdge.Conditions, LINQ - No comments / No trackbacks - §

The code samples on my weblog are colorized using javascript, but you disabled javascript (for my website) on your browser. If you're interested in viewing the posted code snippets in color, please enable javascript.

No comments: