Introduction
A while ago, Microsoft did consider allowing supplying default values for non-value types. Nothing came out of it except maybe Code Contracts, so if you need to allow for a null value for say a List<string>
parameter, you have no choice, but to check for null and assign a default value yourself, or modify your logic flow to accommodate various scenarios. While this is a perfectly acceptable programming practice, sometime, you are just "to busy" (aka lazy) to implement it. This article will demonstrate how Aspect Oriented Programming, to be more specific, its PostSharp flavor, simplifies the task. This article will nto talk in detail about AOP or PostSharp. The reader is expected to do it in their own time.
Using the code
Let's start by showing the sample code:
[assembly: ConsoleApplication1.ApplyDefaults(AttributeTargetMembers = "regex:.*WithDefaults")]
namespace ConsoleApplication1
{
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using PostSharp.Aspects;
using PostSharp.Extensibility;
internal class Program
{
public static DataSet GetsDataWithDefaults([Default] List<string> parameters = null)
{
try
{
var dsResult = new DataSet();
return dsResult;
}
catch (Exception ex)
{
return null;
}
}
private static void Main(string[] args) { DataSet ds = GetsDataWithDefaults(); }
}
[Serializable]
[MulticastAttributeUsage(MulticastTargets.Method)]
public class ApplyDefaults : MethodInterceptionAspect
{
public override void OnInvoke(MethodInterceptionArgs args)
{
var parameterInfos = (from arg in args.Method.GetParameters() where arg.MayBeNull() &&
(args.Arguments[arg.Position] == null) select arg).Cast<ParameterInfo>();
foreach (var info in parameterInfos)
{
Type t = info.ParameterType;
args.Arguments[info.Position] = Activator.CreateInstance(t);
}
args.Proceed();
}
}
[AttributeUsage(AttributeTargets.Parameter)]
public class DefaultAttribute : Attribute
{
}
public static class ReflectionExtensions
{
public static bool MayBeNull(this ParameterInfo arg) {
return (arg.NeedsDefault() && !arg.IsOut); }
private static bool IsCustomAttributeDefined<T>(
this ICustomAttributeProvider value) where T : Attribute { return value.IsDefined(typeof(T), false); }
private static bool NeedsDefault(this ICustomAttributeProvider value) {
return value.IsCustomAttributeDefined<DefaultAttribute>(); }
}
}
The only place in PostSharp where you can intercept and change parameter values is MethodInterceptionAspect
. The implementation is fairly simple, by overriding the OnInvoke
method, checking for a nullable parameter that is null and has been decorated with a [Default]
attribute, than using Reflection (Activator
) to create an instance of the class using discovered type and default public constructor. Because of this approach, it is recommended only to use it with parameter-less constructors. However, it is entirely possible to extend this method and provide an array of objects to use a constructor that best matches the specified number and type of parameters.
The aspect is than applied using multicast, to all methods, which name ends with WithDefaults
. This is to improve performance by eliminating methods, which do not require this particular approach. However, you could apply the aspect to all methods in a class or assembly, as well as each method specifically, by decorating it with the ApplyDefaults
attribute.
Points of Interest
AOP
is a great time saver to eliminate lines of boiler plate code. Like any technology it has to be used with caution and on as-needed basis.
History
-
Version 1.0: Posted on 9/11/2013.
- Version 1.0.1 - Minor mod to code to allow all parameters to be processed, not just first.