3

I was reading this question, and read this response

This is actually a fantastic feature. This lets you have a closure that accesses something normally hidden, say, a private class variable, and let it manipulate it in a controlled way as a response to something like an event.

You can simulate what you want quite easily by creating a local copy of the variable, and using that.

Would we need to implement Lock() in this situation?

What would that look like?

According to Eric Lippert a compiler makes code look like this:

private class Locals
{
  public int count;
  public void Anonymous()
  {
    this.count++;
  }
}

public Action Counter()
{
  Locals locals = new Locals();
  locals.count = 0;
  Action counter = new Action(locals.Anonymous);
  return counter;
}

What does the Lambda would look like, as well as the long-form code?

Community
  • 1
  • 1
TLDR
  • 1,198
  • 1
  • 12
  • 31
  • Mutexes and closures are, in the main, orthogonal concepts. – Ani Jan 24 '11 at 05:15
  • I do see that they are different. What is throwing me is that 3 things are going on: Delegates/Events, Closures, and more than one statement within the lambda. All examples I've seen are one-line lambdas, and I'm looking at as many examples as I can find – TLDR Jan 24 '11 at 05:23

4 Answers4

5

If you have a reason to lock, then yes, there's nothing stopping you from putting a lock statement in a closure.

For example, you could do this:

public static Action<T> GetLockedAdd<T>(IList<T> list)
{
    var lockObj = new object();
    return x =>
    {
        lock (lockObj)
        {
            list.Add(x);
        }
    }
}

What does this look like, in terms of compiler-generated code? Ask yourself: what is captured?

  • A local object used for locking.
  • The IList<T> passed in.

These will be captured as instance fields in a compiler-generated class. So the result will look something like this:

class LockedAdder<T>
{
    // This field serves the role of the lockObj variable; it will be
    // initialized when the type is instantiated.
    public object LockObj = new object();

    // This field serves as the list parameter; it will be set within
    // the method.
    public IList<T> List;

    // This is the method for the lambda.
    public void Add(T x)
    {
        lock (LockObj)
        {
            List.Add(x);
        }
    }
}

public static Action<T> GetLockedAdd<T>(IList<T> list)
{
    // Initializing the lockObj variable becomes equivalent to
    // instantiating the generated class.
    var lockedAdder = new LockedAdder<T> { List = list };

    // The lambda becomes a method call on the instance we have
    // just made.
    return new Action<T>(lockedAdder.Add);
}

Does that make sense?

Dan Tao
  • 125,917
  • 54
  • 300
  • 447
2

Yes it is possible.

Just make sure you do not mutate the locked object instance, else it will be useless.

leppie
  • 115,091
  • 17
  • 196
  • 297
  • Isn't the whole point of locking to be able to mutate the thing that's locked? Did you mean "do not mutate the unlocked object"? – Gabe Jan 24 '11 at 05:22
  • @Gabe: I refer to the object instance being passed to `Monitor.Enter`. – leppie Jan 24 '11 at 05:25
1

You could have a function like this:

static Func<int> GetIncrementer()
{
    object locker = new object();
    int i = 0;
    return () => { lock (locker) { return i++; } };
}

When you call it, it will return a function that increments an internal counter in a thread-safe manner. Although not the best way to implement such a function, it does demonstrate a lock inside of a closure.

Gabe
  • 84,912
  • 12
  • 139
  • 238
  • So whitespace doesn't matter, and I can put any code I want within the lambda? I see now... – TLDR Jan 24 '11 at 05:45
  • @EnduroUSATour: No, whitespace does not matter in C#. If you have more than a single expression, you just put braces around it and use a `return` and your closure function can be as complicated as you want. – Gabe Jan 24 '11 at 06:11
0

I came across this on my internet travels and I know it's a very old question, but I thought I'd propose an alternative answer to it.

It is possible to lock inside a lambda with the help of a wrapper function, which allows a relatively elegant syntax.

Here's the helper function (in a static class):

public static class Locking
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    [DebuggerNonUserCode, DebuggerStepThrough]
    public static T WithLock<T>(this object threadSync, Func<T> selector)
    {
        lock (threadSync)
        {
            return selector();
        }
    }
}

And here's how you use it:

private readonly object _threadSync = new object();
private int _myProperty;

public int MyProperty
    => _threadSync.WithLock(() => _myProperty);
James Cane
  • 123
  • 1
  • 8
  • The DebuggerNonUserCode and DebuggerStepThrough attributes are to aid with debugging. I've found that I still rarely want to step into tiny little functions like this even when I've got the "User code only" option deactivated. – James Cane Jun 27 '18 at 09:29