views:

31

answers:

2

Imagine I have a C# app sitting on a server somewhere that is creating instances of the Item class and publishing them on a messaging service.

class Item
{
    public int ID1, ID2, ID3;
    public double Value1, Value2, Value3;
}

Now I have another C# app on a desktop somewhere listening to these messages. I want to be able to create callbacks based on specified values of the ID# fields. For example, "call this method whenever a message comes in where ID1 = 2, ID2 = 160, and ID3 = anything". I think this would be straightforward if I used string key-value pairs, but ideally I could do this without giving up static typing. I imagine this requires reflection, but I'm not entirely sure where to start.

I'm picturing the app creating an instance of Item with the required ID# values (let's say -1 means unspecified), and passing that into a RegisterCallback method of some object ItemListener. Then whenever ItemListener receives a new Item message, it can check for any callbacks that match, and act accordingly. Is this a reasonable design? Any suggestions on what to look at to implement it?

+1  A: 

It's possible to do without giving up type safety. What you basically need is a tuple type (.NET 4.0 has this, but if you are not using that, you can create your own easily) where the members are nullable types of the id types that you want to match on.

You would then use instances of these types as keys in a dictionary where the value is the delegate that you would execute on a match. For example, your key type would look like this:

struct ItemKey
{
    public int? ID1, ID2, ID3;
    public double? Value1, Value2, Value3;
}

Then, for your example of processing a message where ID1 = 1, ID2 = 160, and ID3 = anything, you would instantiate the key for the delegate in the dictionary like this:

new ItemKey { ID1 = 1, ID2 = 160 }

Note, it's very important here that ItemKey is a structure, as it provides the correct implementations of GetHashCode and Equals which are essential to being keyed in the dictionary correctly.

A drawback of this design is that you have to be explicit for all the kinds of partial matches that are possible.

For example, if you wanted a delegate to be processed when the ID1 = 2, and another one where you want to process when ID1 = 2 and ID2 = 3, you have to specify those cases specifically. This can cause the dictionary to grow very very fast if you have many permutations.

If this is the case, you might want to look into a database solution of some kind where you store the ID values in separate columns, and then do a lookup on the appropriate columns (filtering on selective columns). Then, you would have a type name in a field on the row, which you could use to create a type instance through reflection, which implements an interface or derives from a base type, which you can create a variable of and call the method which performs the functionality.

casperOne
+3  A: 
class ConditionalCallback()
{
     public Predicte<Item>   Predicate {get; set;}
     public Action<Item>     Callback  {get; set;}
}

List<ConditionalCallback> callbacks = new List<ConditionalCallback>();

public AddCallBack(Predicte<Item> pred, Action<Item> callback)
{
    callbacks.Add(new ConditionalCallback { 
                              Predicate = pred, 
                              Callback = callback
                               });
}


void HandleItem(Item item)
{
     foreach(var cc in callbacks)
         if (cc.Predicate(item))    
             cc.Callback(item);
}

// 

AddCallBack( i=> i.ID1 = 2 && i.ID2 = 160 && i.ID3 = anything", MyCallback);
James Curran
i like this solution
Stan R.
thanks, pretty awesome solution.
toasteroven