1

Related to C# - Is there a better alternative than this to 'switch on type'?

I need to 'switch on types': given a parameter of type T, find and execute a method of the form void Method (T param).

This could be solved with a switch statement or a Dictionary<Type, Action>, however I would like to avoid the casts necessary in such scenarios.

I couldn't find the following approach mentioned in the above (or similar) questions:

  1. Create a static generic type that acts as collection:

    public static class Commands<T> {
      public static Action<T> Handler;
    }
    
  2. Create a repository which uses that type like a type-safe dictionary:

    public class CommandRepository {
        public void Register<T>(Action<T> handler) {
            Commands<T>.Handler = handler;
        }
    
        public void Run<T>(T parameter) {
            // null checks etc.
            Commands<T>.Handler(parameter);
        }
    }
    
  3. Example usage:

    public void CreateUser(CreateUserParams p) {
        Console.WriteLine("Creating " + p.Name);
    }
    
    // ...
    var repo = new CommandRepository();
    
    repo.Register<CreateUserParams>(CreateUser);
    repo.Register<DeleteUserParams>(DeleteUser);
    
    repo.Run(new CreateUserParams { Name = "test" });
    repo.Run(new DeleteUserParams { Name = "test" });
    

As mentioned before, the same behavior could be achieved with a Dictionary<Type, Action> in the ComandRepository, but then I would have to cast either the method parameter or, if I use interfaces instead of Action, cast to an IFoo<T> after acquiring an dictionary item.

My question is: Is it OK to (ab-)use generic types like that (given a large number of possible values for T)?

(Bonus question) If it isn't OK, why exactly not? What are the costs / negative effects this would cause?

A final note: I realize this doesn't work for type hierarchies or interfaces. Type T must be matched exactly, which in my scenario is fine.

Edit: I found out that Jil, a JSON serializer, also relies on this pattern. See the TypeCache type, which stores a delegate to serialize an object of type T (as far as I've understood from skimming through code). Since this TypeCache will store a large amount of types I suppose the pattern is generally not problematic.

It would still be interesting to know whether types or their static members need to be garbage collected or if there are other performance implications that need to be considered.

Community
  • 1
  • 1
enzi
  • 4,057
  • 3
  • 35
  • 53
  • Isn't it the way we should use generics? They were made to do things like that, am I right? – Kamil Budziewski Aug 27 '13 at 13:12
  • 3
    "Generic" means that the *same* code is used for *all* types that meet the constraints. If you want different code for different types, generics are most likely the wrong tool. – dtb Aug 27 '13 at 13:13
  • Does all your types are primitive? If yes check out my answer [here](http://stackoverflow.com/a/17774379/2530848) – Sriram Sakthivel Aug 27 '13 at 13:18
  • 2
    This is exactly how several systems at my job work. I don't see how it's 'abusing', it seems like a fine way to use generics to me. For us it is used for domain models that process commands. Every command obviously needs different handling, and so we register handler methods to command types. – Dennisch Aug 27 '13 at 13:20
  • @Dennisch The reason I think this might be abusive is that I'll end up creating a large amount of generic types (`Commands`) and I'm not sure if this would cause any trouble. (Edit) I intend to use this pattern in a similar way as you describe. Good to know someone else is doing it. – enzi Aug 27 '13 at 13:22
  • This sounds like a textbook example of the [Command pattern](http://en.wikipedia.org/wiki/Command_pattern). Have you considered using it? – dtb Aug 27 '13 at 13:31
  • The purpose of this (simplified) example is to map command parameters to commands. Given a parameter of type `T`, call the associated command. I'm trying to implement something similar to [Domain Events](http://www.udidahan.com/2009/06/14/domain-events-salvation/). – enzi Aug 27 '13 at 13:37
  • So you basically need [Double dispatch](http://en.wikipedia.org/wiki/Double_dispatch). In languages like C# that provide only single dispatch, this is usually solved using the [Visitor pattern](http://en.wikipedia.org/wiki/Visitor_pattern). You'd have to extend your visitor class every time you add a new command, but with your current code you already have to register a new handler, so there's no big difference. – dtb Aug 27 '13 at 13:45
  • I have no idea how one would apply the Visitor pattern here. What do you mean with "extend your visitor class"? Adding a Visit method to a class vs. calling a simple method is a very large difference, in my book at least. – enzi Aug 27 '13 at 13:59
  • I will post an example as an answer, because it doesn't fit in a comment... – dtb Aug 27 '13 at 14:05
  • I don't see how having a lot of types causes any problems. – Dennisch Aug 27 '13 at 14:09
  • If "given a parameter of type T, find and execute a method of the form void Method (T param)" describes exactly what you have to do, why generics at all? You could just overload: `void Run(CreateUserParams p) {...}`, `void Run(DeleteUserParams p) {...}`, etc. – wborgsm Aug 27 '13 at 14:26
  • The repository only establishes a mapping between command and parameter, it does not contain the actual logic. In my example, `CreateUser` and `DeleteUser` could e.g. be members of different types. – enzi Aug 27 '13 at 14:30
  • So what about `void Run(CreateUserParams p) { createDelegate(p); }`, `void Run(DeleteUserParams p) { deleteDelegate(p); }` etc., being fooDelegate the delegates for the actual methods to be called, defined as members of your Repository class? – wborgsm Aug 27 '13 at 14:45
  • If your solution works for you, go for it. Whether this is an abuse of generics or not seems to be primarily opinion based. The only hard drawback that I see is that you cannot have multiple CommandRepository instances, because all instances share the same storage for the handlers (the static Commands field); i.e., your CommandRepository is a singleton with all the drawbacks of singletons. – dtb Aug 27 '13 at 14:45
  • @ifdefdebug that would mean that each time I create a command and parameter I would have to add a delegate property to the CommandRepository. In your example, one could also just expose the delegate as property, there's no need for a method that calls it. – enzi Aug 27 '13 at 15:15

2 Answers2

1

The approach you suggest is workable but has the disadvantage that your repository is effectively a singleton. If you ever find yourself needing a repository which doesn't behave like a singleton, you may find the ConditionalWeakTable[1] type helpful. The trick with using one of those is that for each type of interest you would have a singleton ConditionalWeakTable which maps your objects to the thing (if any) associated with that type. That class is only available in .NET 4.0, but can do some wonderful things.

[1] http://msdn.microsoft.com/en-us/library/dd287757.aspx

As an example, suppose one wanted a type OutTypeKeyedDictionary which supports a SetValue<T>(T Value) and bool TryGetValue<T>(out T Value). One could use a static class family OutputMappers<T>, each class of which which held a singleton instance of ConditionalWeakTable<OutTypeKeyedDictionary, T>. The OutTypeKeyedDictionary wouldn't actually have any fields(!); rather, each instance would be used purely as an identity token which would be used as a ConditionalWeakTable key. Incidentally, the reason that class is in the CompilerServices namespace rather than Collections is that it is used heavily by things like ExpandoObject.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • Very Interesting! First time I hear of that type. I'm not quite sure I grasp the meaning correctly though; in my example above, would you suggest that I use a `static readonly ConditionalWeakTable>` field in my `Commands` type and then map actions to a `CommandRepository` instance to fix the Singleton problem? In other words, the whole point of the `ConditionalWeakTable` would be to allow multiple `CommandRepository` instances? (which would be an immense improvement) – enzi Aug 27 '13 at 19:33
  • One more point: I suppose the `ConditionalWeakTable` is more or less a dictionary that uses a `WeakReference` for its key – do you happen to know if that's the case? Or if not, do you know what other magic is driving that type / if it there are any performance considerations to take into account? Otherwise this sounds like an ideal fit, whether or not a Singleton solution would be sufficient. – enzi Aug 27 '13 at 19:40
  • @enzi: See update above. Also, the `ConditionalWeakTable` uses some special magic (which wasn't available before 4.0) to ensure that whatever kind of references exist to the key (strong, finalizer-queue, etc.) will also be deemed to exist to the value. I don't know what the exact performance considerations are, but it was designed for a use quite similar to what I described. – supercat Aug 27 '13 at 19:45
  • @enzi: One minor limitation of the approach as described is that there is no practical way to find all of the data associated with a particular `OutTypeKeyedDictionary` instance, since it's scattered throughout many different tables, and the instance doesn't hold a reference to any of them. There are ways around that, but they're a little tricky. – supercat Aug 27 '13 at 19:55
  • Thanks for the update. The limitation you mention is only natural I think; if I store values in a `Type`, how would I ever be able to enumerate the values? By enumerating all possible values of `T` and checking if it contains something (impractical) or using some sort of supportive collection I suppose – I do not intend to do that, so that's no issue for me. Thank you for your answer; I'll leave the question unanswered for one more day or so and if there are no other answers I'll accept yours. – enzi Aug 27 '13 at 20:12
  • @enzi: It's worth noting that while `ConditionalWeakTable` is thread-safe, that comes at a cost: entries can neither be modified nor deleted (it's a lot easier to make add-only collections thread-safe than modifiable ones). For that reason, it may be helpful to define a private `SimpleHolder` class which has a public field of type `T` along with a `ReturnThis` method. Have each CWT hold a `SimpleHolder` as its value, rather than holding a `T` directly. The first time a value is set, create a `SimpleHolder` to hold it and store a reference to that in the CWT. – supercat Aug 28 '13 at 14:49
  • If one wants things to be efficient and thread-safe, your `set` method should start by using `TryGetValue` to see if the CWT has an entry. If not, create a new `SimpleHolder`, populate it, and pass a delegate to its `ReturnThis` method to the `GetValue` method (curious there's no overload that takes the value you want stored in the table). If some other thread stores a value in the meantime, don't worry; pretend your store succeeded just before the other thread's did and was overwritten. – supercat Aug 28 '13 at 14:54
  • @enzi: BTW, although one can't read data out of a collection polymorphically, one can design a polymorphic collection which, given something that implements an interface with a generic polymorphic method, will invoke that method generically on everything in the collection. – supercat Aug 28 '13 at 15:11
-1

The usual way to implement double dispatch in language like C# that provide only single dispatch, is the Visitor pattern. Your example would look as follows:

Interfaces:

interface IVisitor
{
    void VisitCreateUserParams(CreateUserParams p);

    void VisitDeleteUserParams(DeleteUserParams p);
}

interface IParams
{
    void Accept(IVisitor visitor);
}

Command parameters:

class CreateUserParams : IParams
{
    public void Accept(IVisitor visitor) { visitor.VisitCreateUserParams(this); }

    public string Name { get; set; }
}

class DeleteUserParams : IParams
{
    public void Accept(IVisitor visitor) { visitor.VisitDeleteUserParams(this); }

    public string Name { get; set; }
}

Commands:

class CommandHandler : IVisitor
{
    public void VisitCreateUserParams(CreateUserParams p)
    {
        Console.WriteLine("Creating " + p.Name);
    }

    public void VisitDeleteUserParams(DeleteUserParams p)
    {
        Console.WriteLine("Deleting " + p.Name);
    }
}

Example usage:

var handler = new CommandHandler();

new CreateUserParams { Name = "test" }.Accept(handler);
new DeleteUserParams { Name = "test" }.Accept(handler);
dtb
  • 213,145
  • 36
  • 401
  • 431