Dynamic registration of collection processors


K

Konstantin Denerz

Hallo,

Auf der Suche nach der Möglichkeit in Spring.Net Expressions (Spring.Expressions) neue Funktionen für Collections zu definieren und zur Laufzeit auf diese zuzugreifen, musste ich feststellen, dass es ohne einen Hack nicht geht.
Hier ist eine Lösung für die dynamische Registrierung von Collection Processors. Zwar ähnelt diese mehr einem Hack als einer sauberen Lösung ist aber anders ohne größere Änderungen am Spring.Net Framework selbst nicht durchzuführen.


Die Idee eine Spring Expression für die Registrierung zu verwenden anstatt direkt über Reflection auf das Map mit den Collection Processors zuzugreifen kam von Thomas Darimont. Ich finde die Idee mittlerweile gut, da in diesem Fall die hierdurch entstehende Flexibilität von Vorteil ist.

Program.cs:

C#:
public static void Main(string[] args)
{
	CollectionProcessorRegistry registry = new CollectionProcessorRegistry();
	registry.RegisterExpression = "T(Spring.Expressions.MethodNode)." +
	"collectionProcessorMap[#theProcessor.Name]=#theProcessor";
	ProcessDelegate processDelegate = delegate(ICollection theSource,
							 object[] theArguments){
		object result = null;
		if(theSource != null)
		{
			ArrayList source = new ArrayList();
			source.AddRange(theSource);
 
			foreach(object argument in theArguments)
			{
				source.Remove(argument);
			}
 
			result = source;
		}
		return result;
	};
	ICollectionProcessor processor =
		new DelegateBasedCollectionProcessor("filter",processDelegate);
	registry.Register(processor);
	string expression = "{'c',2,1,'x','#'}.filter('x',2,1)";
	ICollection resultCollection =
		(ICollection)ExpressionEvaluator.GetValue(null,expression);
	foreach(object resultElement in resultCollection)
	{
		Console.Write(resultElement);
	}
	Console.WriteLine();
	Console.Write("Press any key to continue . . . ");
	Console.ReadKey(true);
}
DelegateBasedCollectionProcessor.cs:
C#:
using System;
using System.Collections;
using Spring.Expressions.Processors;
 
namespace Spring.Expressions.Processors
{
	/// <summary>
	/// Delegate used to delegate to process method.
	/// </summary>
	public delegate object ProcessDelegate(ICollection theSource, object[] theArguments);
 
	/// <summary>
	/// Serves as processor of collections.
	/// </summary>
	/// <author>konstantin.denerz</author>
	public class DelegateBasedCollectionProcessor: ICollectionProcessor
	{
 
		#region Constructors
 
		/// <summary>
		/// The default constructor.
		/// </summary>
		/// <param name="theName">The name of the collection processor.</param>
		/// <param name="theProcessDelegate">The delegate used to delegate to the process method.</param>
		public DelegateBasedCollectionProcessor(string theName, ProcessDelegate theProcessDelegate)
		{
			this.name = theName;
			this.processDelegate = theProcessDelegate;
		}
 
		#endregion
 
		#region Properties
 
		/// <summary>
		/// Holds the name of the collection processor.
		/// </summary>
		public string Name 
		{
			get { return name; }
			set { name = value; }
		} private string name;
 
 
		private ProcessDelegate processDelegate;
 
		#endregion
 
		#region API
 
		/// <summary>
		/// Process the collection.
		/// </summary>
		/// <param name="theSource">The source collection.</param>
		/// <param name="theArguments">The arguments.</param>
		/// <returns>Return the result of the process.</returns>
		public object Process(ICollection theSource, object[] theArguments)
		{
			return processDelegate(theSource,theArguments);
		}
 
		/// <summary>
		/// Return the name of the collection processor.
		/// </summary>
		/// <returns>The name of the collection processor.</returns>
		public override string ToString()
		{
			return name;
		}
 
 
		#endregion
	}
}
CollectionProcessorRegistry.cs:
C#:
using System;
using System.Collections;
using Spring.Expressions;
using Spring.Expressions.Processors;

namespace Spring.Expressions.Processors
{
	/// <summary>
	/// Serves as registry service for collection processors.
	/// </summary>
	/// <author>konstantin.denerz</author>
	public class CollectionProcessorRegistry
	{
		
		#region Properties
		
		/// <summary>
		/// Holds the expression string used to register a collection processor.
		/// </summary>
		public string RegisterExpression 
		{
			get { return registerExpression; }
			set { registerExpression = value; }
		} private string registerExpression;
	
		#endregion
		
		#region API
		
		/// <summary>
		/// Register a collection processor so that can be used in spring expressions.
		/// </summary>
		/// <param name="theProcessor">The processor that should be registered.</param>
		public void Register(ICollectionProcessor theProcessor)
		{
			IDictionary variables = new Hashtable();
			variables["theProcessor"] = theProcessor;
			ExpressionEvaluator.GetValue(null,RegisterExpression,variables);
		}
		
		#endregion
		
	}
}

Der Code wurde mit .Net 2.0 / SPRING.net 1.1 getestet.

Gruß Konstantin
 

Norbert Eder

Erfahrenes Mitglied
Wozu hier etwas wie Spring verwenden?

Queries auf Objekte können einfach per LINQ to Objects gemacht werden. Collections können über Extension Methods erweitert werden.

Data-Object
C#:
public class Person
{
    public int Id { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
}

Extension Method für unsere Liste:
C#:
static class PersonListExtension
{
    public static XmlDocument ToXml(this List<Person> personList)
    {
        XmlDocument doc = new XmlDocument();
        XmlNode xnRoot = doc.CreateElement("PersonList");
        doc.AppendChild(xnRoot);

        foreach (Person p in personList)
        {
            XmlNode xnPerson = doc.CreateElement("Person");
            XmlNode xnId = doc.CreateElement("Id");
            XmlNode xnFirstname = doc.CreateElement("Firstname");
            XmlNode xnLastname = doc.CreateElement("Lastname");

            xnId.InnerText = p.Id.ToString();
            xnFirstname.InnerText = p.Firstname;
            xnLastname.InnerText = p.Lastname;

            xnPerson.AppendChild(xnId);
            xnPerson.AppendChild(xnFirstname);
            xnPerson.AppendChild(xnLastname);

            xnRoot.AppendChild(xnPerson);
        }
        return doc;
    }
}

Und hier unser Test:
C#:
List<Person> personList = new List<Person>();

for (int i = 0; i < 10; i++)
{
    Person p = new Person() { Id = i, Firstname = "Norbert", Lastname = "Eder" };
    personList.Add(p);
}

var pList = from p in personList
            where p.Id < 5
            select p.Id + " " + p.Lastname + " " + p.Firstname;

Console.WriteLine("---PERSONLIST-XML---");
Console.WriteLine(personList.ToXml().OuterXml);
Console.WriteLine("---QUERY-RESULT---");
foreach (string s in pList)
    Console.WriteLine(s);

Die Abfragen können natürlich komplexer gestaltet werden, zudem kann ich zu jedem beliebigen Typen eine Method dranhängen. So gesehen ist meiner Meinung nach Spring an dieser Stelle überflüssig. Zudem sieht diese Variante auch wesentlich schöner aus ;-)
 
K

Konstantin Denerz

Hallo Norbert,

Dein Beispiel ist schön, aber soweit ich weiß gibt es LINQ erst ab dem .NET-Framework 3.5. Die meisten Unternehmen werden wohl noch die Version 2.0 nutzen.
Mein Beispiel sollte eine Lösung für diejenigen sein, die viel mit Spring Expressions arbeiten und mehr als nur die Standard Funktionen, wie count, max, min, distinct usw. für Collections brauchen. Man kann natürlich auch folgendermaßen Methoden für die Verarbeitung von Collections ausführen:
C#:
//die Expression
string maxExpression = "T(MyNamespace.MyClass).max({1,25,5,3,0})"

Sieht natürlich nicht so schön aus wie folgender Code:
C#:
//die Expression
string maxExpression = "{1,25,5,3,0}.max()"

Gruß Konstantin
 
Zuletzt bearbeitet von einem Moderator:

Norbert Eder

Erfahrenes Mitglied
Gerade neue Projekte werden bereits vielfach mit .NET 3.5 umgesetzt und daher ein Beispiel um zu zeigen, dass für derartige Fälle Spring nicht benötigt wird.