Friday, May 22, 2009

Putting C# attributes to use


Recently in an interview I was asked if I had worked with attributes, I had, and I told them how, I don’t know why I didn’t put it on my blog, anyways now I will do it.

What I have done is that, I have created 2 attribues [notNULL] and [notNIL] these can be applied to any function parameter, what happens is, if you pass null to a function that has the parameter decorated as [notNULL] then a ArgumentNullException is thrown and for [notNIL] ArgumentException is thrown.

To achieve this, the class which is going to use these attributes must be decorated with another attribute ([the] in our case) this attribute adds a property for itself and this property adds a sink where you get a hook and can query the method/parameter/etc and execute any business logic you want – in my case I am simply checking if the parameter has the said attributes and throw exception.

The Client Program (Program.cs)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

using System.Runtime.Remoting.Activation;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
theClass theObject = new theClass();
theObject.operate("a",3,null);

theChild theChildObject = new theChild();
theChildObject.working("");
}
}
}

The Class (theClass.cs)

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
[the]
class theClass:ContextBoundObject
{
public void operate([notNULL]string s, int a, [notNIL]string ss)
{
Console.WriteLine("theClass did its work");
}
}
[the]
class theChild : theClass
{
public void working([notNULL] string s)
{
Console.WriteLine("theChild did its work");
}
}
}

NotNULL/NotNIL Attributes (notNULL.cs)

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
[AttributeUsage(AttributeTargets.Parameter)]
class notNULLAttribute:Attribute
{
public notNULLAttribute() { }

}
[AttributeUsage(AttributeTargets.Parameter)]
class notNILAttribute : Attribute
{
public notNILAttribute() { }

}
}

The Attribute (theAttribute.cs)

using System;
using System.Collections.Generic;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Activation;
using System.Text;

namespace ConsoleApplication1
{
[AttributeUsage(AttributeTargets.Class,Inherited=true)]
class theAttribute:Attribute,IContextAttribute
{
public theAttribute()
{ }

public void GetPropertiesForNewContext(IConstructionCallMessage msg)
{
theProperty property = new theProperty();
msg.ContextProperties.Add(property);
}

public bool IsContextOK(Context ctx, IConstructionCallMessage msg)
{
return false;
}
}
}

The Property (theProperty.cs)

using System;
using System.Collections.Generic;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Messaging;
using System.Text;

namespace ConsoleApplication1
{
class theProperty:IContextProperty,IContributeServerContextSink
{
public theProperty() { }

public string Name { get { return "MyProperty"; } }

public bool IsNewContextOK(Context newctx) { return true; }

public void Freeze(Context newContext){}

public IMessageSink GetServerContextSink(IMessageSink nextSink)
{
return new theSink(nextSink);
}
}
}

The Sink (theSink.cs)

using System;
using System.Collections.Generic;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Messaging;
using System.Reflection;
using System.Text;

namespace ConsoleApplication1
{
class theSink:IMessageSink
{
private IMessageSink next;

public theSink(IMessageSink next) { this.next = next; }

public IMessage SyncProcessMessage(IMessage msg)
{
if (msg is IMethodMessage)
{
IMethodMessage methodMessage = msg as IMethodMessage;

MethodBase methodInfo = methodMessage.MethodBase;

ParameterInfo[] parameters = methodInfo.GetParameters();
foreach (ParameterInfo p in parameters)
{
object[] attributes = p.GetCustomAttributes(true);
foreach (Attribute a in attributes)
{
if (a.GetType() == typeof(notNULLAttribute))
{
//Console.WriteLine("found " + p.ToString() + " at position "+p.Position+" which has notnull attribute");
if (null == methodMessage.GetArg(p.Position))
{
Console.WriteLine("Error : " + p.ToString() + " cannot be null in "+methodInfo.Name);
throw new ArgumentNullException("Not null attribute violated");
}
}
if (a.GetType() == typeof(notNILAttribute))
{
//Console.WriteLine("found " + p.ToString() + " at position "+p.Position+" which has notnull attribute");
if(null == methodMessage.GetArg(p.Position) ||
"" == methodMessage.GetArg(p.Position).ToString())
{
Console.WriteLine("Error : " + p.ToString() + " cannot be nil in "+methodInfo.Name);
throw new ArgumentException("Not nil attribute violated");
}
}
}
}
}
IMessage replyMessage = next.SyncProcessMessage(msg);
return replyMessage;
}

public IMessageSink NextSink { get { return this.next; } }

public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
{
return null;
}
}
}

No comments: