tags:

views:

262

answers:

6

hi.
I have lots(+2000) of GUIDs(in some network class) and my program must find one of them when it receives a message and do the job associated with it.
the positive point is i have a hard-code generator, but the fastest way is my goal(and i don't know how to implement it).

my code should do something like this:

switch(received guid)  
{  
case guid1: do job 1; break;  
case guid2: do job 2; break;  
case guid3: do job 3; break;  
case guid4: do job 4; break;  
....  
}  
+14  A: 

You could create a Dictionary with the Guid as the key and a delegate reference as the value. That would ensure fast lookups.

Brian Rasmussen
Isn't a dictionary exactly what switch would build under the covers anyway? :p
Randolpho
@Randolpho: That depends on the compiler. But the problem is C# only allows switch on integral types and strings.
Brian Rasmussen
@Randolpho: Yes, in certain circumstances, the C# compiler might generate code that fills a static dictionary and uses this. In other scenario's it just generates a big list of if else statements. Still, there's a big difference between writing an big unmaintainable switch and using a dictionary, no matter what the compiler generates under the covers.
Steven
@Steven: ok, I agree. I was just pointing it out. :) @Brian Rasmussen: very true. I figured anyone who switched on a Guid would switch on its hashcode or string value.
Randolpho
+6  A: 

Use a hashtable which maps Guid to a delegate or a class that represents the task, such as Dictionary<Guid, Action> or Dictionary<Guid, Task>.

Paul Ruane
I think that's the sane way to do what he wants.
kervin
+4  A: 

A Dictionary<Guid, JobDelegate> would probably be faster than a switch statement.

But you would have to profile to be sure.

Henk Holterman
I'm not sure about faster... but it would be much nicer to maintain for 2000+ guids.
Matthew Whited
@Matthew: the OP mentions a code-generator. I'm not sure about the details.
Henk Holterman
True, but just because code is generated doesn't mean it isn't maintained.
Matthew Whited
When code is generated, maintenance usually means: re-generate.
Henk Holterman
Performance would be about the same, since C# will generate a Dictionary under the covers for big switch blocks. But as Matthew already said, maintainability is most of the time far more important than those few extra milliseconds or probably even microseconds.
Steven
+8  A: 

Create an interface for doing the Job, then implement 2000 classes which do the work, each of which knows its own guid. Then add the classes to a dictionary using its guid as the key. Then when you get the guid, you look the object up in the dictionary and call the method on the interface.

public interface IJobDoer
{
    void DoJob();
    Guid Guid{get;}
}

public class FirstJobType : IJobDoer
{
    void DoJob()
    {
     /// whatever...
    }
    Guid Guid { get{return "insert-guid-here";}}
}
Sam Holder
nice idea, i am going to implement it.(it does less boxing/unboxing than delegate)
Behrooz
the thing I like about this is that it gives you good separation of concerns as each class only has the code for the job it is responsible for.
Sam Holder
Am I really the only person that sees maintaining 2000 classes being a problem? How is this even a remotely suitable solution?
Programming Hero
i said that i have a hard-code generator.and it solves everything.
Behrooz
@Programming Hero: you have to manage the 2000 'job handlers' some way. would managing 2000 delegates be any easier. I would have thought that having the classes divided up into some appropriate namespaces would help, but basically you are going to have to manage them somehow and having each one in its own class, with the functionality all in one place seems as good a solution as any to me.
Sam Holder
@Behrooz: Why would delegates have anything to do with Boxing/Unboxing?
kervin
A: 

I like to show a variation of the dictionary approach others already proposed. Building on this that solution, you could do the following.

1 Define a base class:

public abstract class JobDoer
{
    public abstract void DoJob();
}

2 Define a attribute for decoration of job doers.

public sealed class JobDoerAttribute : Attribute
{
    JobDoerAttribute(string jobDoerId)
    {
        this.JobDoerId = new Guid(jobDoerId);
    }

    public Guid JobDoerId { get; private set; }
}

3 Define the actual job doer classes that are decorated with that attribute. For instance:

[JobDoer("063EE2B2-3759-11DF-B738-49BB56D89593")]
public sealed class SpecificJobDoer : JobDoer
{
    public override void DoJob()
    {
        // Do a specific job
    }
}

4 Define a JobDoerFactory that enables retrieving JobDoer instances by their Id as it is defined in the attribute:

public static class JobDoerFactory
{
    static Dictionary<Guid, JobDoer> cache;

    static JobDoerFactory()
    {
        // Building the cache is slow, but it will only run once 
        // during the lifetime of the AppDomain.
        cache = BuildCache();
    }

    public static JobDoer GetInstanceById(Guid jobDoerId)
    {
        // Retrieving a JobDoer is as fast as using a switch statement.
        return cache[jobDoerId];
    }

    private static Dictionary<Guid, JobDoer> BuildCache()
    {
        // See implementation below.
    }
}

In the BuildCache method, you can do the loading of JobDoer instances by using reflection.

private static Dictionary<Guid, JobDoer> BuildCache()
{
    // This is a bit naive implementation; we miss some error checking,
    // but you'll get the idea :-)
    var jobDoers =
       (from assembly in AppDomain.CurrentDomain.GetAssemblies()
        from type in assembly.GetTypes()
        where type.IsSubclassOf(typeof(JobDoer))
        let attributes =
            type.GetCustomAttribute(typeof(JobDoerAttribute), true)
        where attributes.Length > 0
        let attribute = attributes[0] as JobDoerAttribute
        select new { attribute.JobDoerId, type }).ToArray();

    var cache = new Dictionary<Guid, JobDoer>(jobDoers.Length);

    foreach (jobDoer in jobDoers)
    {
        // Note that actually a single instance of the job doer is
        // cached by ID. This means that every Job Doer must be 
        // thread-safe and usable multiple times. If this is not 
        // feasable, you can also create store a set of Func<JobDoer> 
        // objects that enable creating a new instance on each call.
        cache[jobDoer.JobDoerId] =
            (JobDoer)Activator.CreateInstance(jobDoer.type);
    }
    return cache;
}

I didn't test this code, so I don't know if it compiles, but I used this mechanism in a project a few years back. This way it is easy to define new classes, without the need to hook it up to some dictionary. It is done automatically at runtime.

This might look a bit like overkill, but if you have +2000 JobDoer classes, this could help you a lot.

Update: Note that if you don't like the idea of the JobDoerAttribute, you can also implement it as abstract property on the abstract JobDoer class. However, I've found using an attribute makes the code very explicit and expressive.

Steven
Nice flexible approach! Keep in mind that reflection isn't your best friend when it comes to performance, but since it's only performed once and then cached, this might not be such of a problem in this case.
Rob van Groenewoud
"but since it's only performed once and then cached". Exactly. Performance of this solution is as good as a `switch` statement, because that's what C# is doing under the covers (creating a dictionary in the static constructor).
Steven
A: 

Create a Dictionary with Guid And Action, and search over it.

Kerem Kusmezer