Question

I have a situation where I need to generate a few similar anonymous delegates. Here's an example:

public void Foo(AnotherType theObj)
{
    var shared = (SomeType)null;

    theObj.LoadThing += () =>
    {
        if(shared == null)
            shared = LoadShared();

        return shared.Thing;
    };

    theObj.LoadOtherThing += () =>
    {
        if(shared == null)
            shared = LoadShared();

        return shared.OtherThing;
    };

    // more event handlers here...
}

The trouble I'm having is that my code isn't very DRY. The contents of each of the event handlers is EXTREMELY similar, and could be easily parameterized into a factory method. The only thing preventing me from doing that is that each delegate needs to share the reference to the shared variable. I can't pass shared to a factory method with the ref keyword, as you can't create a closure around a ref varaiable. Any ideas?

Was it helpful?

Solution

There's no problem that can't be solved by adding more abstraction. (*)

The pattern you are repeating over and over again is the "lazy loading" pattern. That pattern is highly amenable to being captured in a type, and in fact, it has been, in version 4 of the framework. Documentation here:

http://msdn.microsoft.com/en-us/library/dd642331.aspx

You could then do something like:

public void Foo(AnotherType theObj)
{
    var shared = new Lazy<SomeType>(()=>LoadShared());
    theObj.LoadThing += () => shared.Value.Thing;
    theObj.LoadOtherThing += () => shared.Value.OtherThing;
    // more event handlers here...
}

And there you go. The first time shared.Value is accessed the value gets loaded; every subsequent time the cached value is used. Extra bonus: this is even threadsafe should the shared value be accessed on multiple threads. (See the documentation for details about precisely what guarantees we make regarding thread safety.)


(*) Except of course for the problem "I have too much abstraction."

OTHER TIPS

Add an extra layer of indirection. Create a class that is just a wrapper for the data that you want to keep:

public class MyPointer<T>
{
  public T Value{get;set;}
}

New up a MyPointer<SomeType> at the start of the method and pass it into the factory. Now the MyPointer reference is copied by value, so you can't change the MyPointer instance, but you can change the Value in each of the factory methods and it is reflected elsewhere.

How about:

public void Foo(AnotherType theObj)
{
    var shared = (SomeType)null;

    Action handler = () =>
    {
        if(shared == null)
            shared = LoadShared();

        return Shared.Thing;
    };

    theObj.LoadThing += handler;
    theObj.LoadOtherThing += handler;

    // more event handlers here...
}

You can also put the Action in a method and pass a parameter:

public void Foo(AnotherType theObj)
{
    var shared = (SomeType)null;

    theObj.LoadThing += () => Handle("LoadThing");
    theObj.LoadOtherThing += () => Handle("LoadOtherThing");

    // more event handlers here...
}

private T Handle<T>(T returnParameter)
{
    if(shared == null)
        shared = LoadShared();

    return returnParameter;
}

I like Eric's method better, but here's another approach borne out of some kind of lisp fueled delirium I think.

var LoaderMaker = (Func<SomeType, int> thingGetter) => {
    return () => {
        if(shared == null) shared = LoadShared();
        return thingGetter(shared);
    };
};

theObj.LoadThing = LoaderMaker(t => t.Thing);
theObj.LoadOtherThing = LoaderMaker(t => t.OtherThing);

I suggest putting the repetitive code in a separate function.

public void Foo(AnotherType theObj)
{
    var shared = (SomeType)null;

    Func<SomeType> getShared = () =>
    {
        if(shared == null)
            shared = LoadShared();
        return shared;
    };

    // Or more compact
    Func<SomeType> getShared = () => shared ?? (shared = LoadShared());

    theObj.LoadThing += () => getShared().Thing;

    theObj.LoadOtherThing += () => getShared().OtherThing;

    // more event handlers here...
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top