HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Generic queries and handlers without reflection

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
genericwithoutreflectionhandlersandqueries

Problem

I'm trying to implement generic queries and handlers so that I can make requests with simple syntax, like this:

var query = new HelloQuery("hi");
var result = processor.Process(query);


This post explains a brilliant technique for accomplishing this, but it uses reflection and dynamic to resolve handlers for queries. I came up with an alternative using generics.

The central idea is the IQuery:

public interface IQuery
    where TSelf : IQuery
{
}


One such implementation could be:

public class HelloQuery : IQuery
{
    public HelloQuery(string message)
    {
        this.message = message;
    }

    private readonly string message;
    public string Message
    {
        get { return this.message; }
    }
}


The TSelf type argument is the key difference between my technique and the other post's technique. It allows us to avoid reflection in the HandlerResolver:

public interface IHandlerResolver
{
    IQueryHandler GetHandlerFor()
        where TQuery: IQuery;
}

public class HandlerResolver : IHandlerResolver
{
    private readonly IContainer container;

    public HandlerResolver(IContainer container)
    {
        this.container = container;
    }

    public IQueryHandler GetHandlerFor()
        where TQuery : IQuery
    {
        var handler = this.container.Get>();
        return handler;
    }
}


You would register handlers at the composition root. Here's the interface for a handler, and an implementation for HelloQuery:

public interface IQueryHandler
    where TQuery : IQuery
{
    TResult Handle(TQuery request);
}

public class HelloQueryHandler : IQueryHandler
{
    public string Handle(HelloQuery request)
    {
        var response = string.Format("Well \"{0}\" yourself!", request.Message);
        return response;
    }
}


And finally, the actual query processor:

```
public interface IQueryProcessor
{
TResult Process(IQuery request)
where TQuery : IQuery;
}

public class QueryProcessor : IQue

Solution

One objection I could raise is that your generic type constraint doesn't guarantee what you think it might guarantee. Eg. you could have:

// TSelf is actually not T of Self
public class HelloQuery : IQuery 
{
}

// This one is fine
public class GoodbyeQuery: IQuery 
{
}


In other words, TSelf doesn't actually have to be the T of the class itself. This is not horrible, especially since you seem to only be using the interface as metadata, but it can still lead to confusing results during dependency injection if you've accidentally screwed up a class definition.

However, as long as you're not a masochist and don't declare nonsensical types like this, there is nothing wrong with this. This is an established design pattern known as the curiously recurring template pattern.

Code Snippets

// TSelf is actually not T of Self
public class HelloQuery : IQuery<GoodbyeQuery, string> 
{
}

// This one is fine
public class GoodbyeQuery: IQuery<GoodbyeQuery, string> 
{
}

Context

StackExchange Code Review Q#79394, answer score: 6

Revisions (0)

No revisions yet.