views:

492

answers:

3

Here's a simplified version of my class:

public abstract class Task
{
    private static object LockObject = new object();

    protected virtual void UpdateSharedData() { }
    protected virtual void UpdateNonSharedData() { }

    public void Method()
    {
       lock(LockObject)
       {
          UpdateSharedData();
       }
       UpdateNonSharedData();
    }
}

I'm trying to hide the locking code from derived classes. But I only want to obtain the lock if the derived class overrides UpdateSharedData; if it doesn't, I don't want the method to block and wait on all of the other running instances that do update shared data before it updates the non-shared data.

So the (seemingly) obvious thing for Method to do is to check and see if the current instance's implementation of UpdateSharedData has overriden the base class's implementation. I'm pretty sure this isn't possible without using reflection, and it's probably not desirable to do that.

I've thought of some workarounds to this, but they're all pretty awkward:

  • Add a protected bool property that the derived class's constructor sets, and check that property to see if a lock is needed. That's doing a pretty terrible job of hiding the locking code from the derived classes.
  • Make the UpdateSharedData method a delegate property, have any derived class set the property to a private method in its constructor, and only obtain the lock if the delegate isn't null. That's better, but it still kind of sucks.

It could just be that I'm not thinking about this problem properly. Or that there's some obvious way of doing this check that I'm just not thinking of. Any ideas?

+3  A: 

You can do this check with a smidge of reflection:

bool IsUpdateSharedDataOverridden()
{
    Type t = this.GetType();
    MethodInfo m = subType.GetMethod("UpdateSharedData");

    return m.DeclaringType == t && m.GetBaseDefinition().DeclaringType == typeof(Task);
}
Chris Hynes
It would be more correct to comparce m.DeclaringType directly to t. It's very possible for two different types to have the same name.
JaredPar
That would also report true if it used "new" to re-declare (not override) the method.
Marc Gravell
Good points. Fixed accordingly.
Chris Hynes
Still doesn't 100% work. A method can be both virtual and "new".
JaredPar
+2  A: 

What if you defined an abstract Task and a IHasSharedData interface, then in Method you check if the derived Task implements IHasSharedData before doing the lock. Only classes that implement the interface need wait. I realize that this avoids answering the actual question, but I think it would be a cleaner solution than using reflection. Hopefully, you'd find a better name for the interface that more closely matches what the classes actually do.

public interface IHasSharedData
{
    void UpdateSharedData();
}

public abstract class Task
{
    private static object LockObject = new object();

    protected virtual void UpdateNonSharedData() { }

    public void Method()
    {
         if (this is IHasSharedData)
         {
            lock(LockObject)
            {
                UpdateSharedData();
            }
         }
         UpdateNonSharedData();
    }
}

public class SharedDataTask : Task, IHasSharedData
{
    public void UpdateSharedData()
    {
       ...
    }
}
tvanfosson
I just realized that IHasSharedData looks a lot like LOLCats code. :-) ICanHazSharedData?
tvanfosson
That was the first thing I noticed too! It's going to be very hard for me to resist giving it a name like that. This is exactly the answer I was looking for nonetheless.
Robert Rossney
A: 

Actually, you are talking about two different objects:

public abstract class Task {    
    protected virtual void UpdateNonSharedData() { }

    public virtual void Method()    
    {   
        UpdateNonSharedData();    
    }
}

public abstract class TaskWithSharedData : Task {    
    private static object LockObject = new object();    

    protected virtual void UpdateSharedData() { }

    public overrides void Method()    
    {       
        lock(LockObject)
        {          
            UpdateSharedData();       
        }   
        base.Method();
    }
}

But, more ideal solution will be the Strategy Pattern.

LicenseQ