I've been working for a while on locking resources (state objects) so that the only way to access them is by acquiring a lock. This moves the responsibility for locking from the user of the state to the state itself. I wanted something with a lot of syntactic sugar so that once the lock is acquired the state object is used via a reference without a need for special getters and setters.
namespace Lockable {
public delegate void ActionRef<T>(ref T r1);
public delegate void ActionIn<T>(in T r1);
public delegate RES FuncRef<T, RES>(ref T r1);
public delegate RES FuncIn<T, RES>(in T r1);
public delegate void ActionRef<T1, T2>(ref T1 r1, ref T2 r2);
public delegate void ActionIn<T1, T2>(in T1 r1, in T2 r2);
public delegate RES FuncRef<T1, T2, RES>(ref T1 r1, ref T2 r2);
public delegate RES FuncIn<T1, T2, RES>(in T1 r1, in T2 r2);
public delegate void ActionRef<T1, T2, T3>(ref T1 r1, ref T2 r2, ref T3 r3);
public delegate void ActionIn<T1, T2, T3>(in T1 r1, in T2 r2, in T3 r3);
public delegate RES FuncRef<T1, T2, T3, RES>(ref T1 r1, ref T2 r2, ref T3 r3);
public delegate RES FuncIn<T1, T2, T3, RES>(in T1 r1, in T2 r2, in T3 r3);
public class Lockable<T> where T : new() {
public Lockable() => this.val = new T();
public Lockable(T val) => this.val = val;
readonly object theLock = new object();
T val;
public void Lock(ActionRef<T> f) { lock (theLock) f(ref val); }
public void Lock(ActionIn<T> f) { lock (theLock) f(in val); }
public TRES Lock<TRES>(FuncRef<T, TRES> f) { lock (theLock) return f(ref val); }
public TRES Lock<TRES>(FuncIn<T, TRES> f) { lock (theLock) return f(in val); }
public class TwoLockable<T2> where T2 : new() {
public TwoLockable(Lockable<T> val1, Lockable<T2> val2) {
this.l1 = val1;
this.l2 = val2;
}
readonly Lockable<T> l1;
readonly Lockable<T2> l2;
public void Lock(ActionRef<T, T2> f) { lock (l1.theLock) lock (l2.theLock) f(ref l1.val, ref l2.val); }
public void Lock(ActionIn<T, T2> f) { lock (l1.theLock) lock (l2.theLock) f(in l1.val, in l2.val); }
public TRES Lock<TRES>(FuncRef<T, T2, TRES> f) { lock (l1.theLock) lock (l2.theLock) return f(ref l1.val, ref l2.val); }
public TRES Lock<TRES>(FuncIn<T, T2, TRES> f) { lock (l1.theLock) lock (l2.theLock) return f(in l1.val, in l2.val); }
public class ThreeLockable<T3> where T3 : new() {
public ThreeLockable(Lockable<T> val1, Lockable<T2> val2, Lockable<T3> val3) {
this.l1 = val1;
this.l2 = val2;
this.l3 = val3;
}
readonly Lockable<T> l1;
readonly Lockable<T2> l2;
readonly Lockable<T3> l3;
public void Lock(ActionRef<T, T2, T3> f) { lock (l1.theLock) lock (l2.theLock) lock (l3.theLock) f(ref l1.val, ref l2.val, ref l3.val); }
public void Lock(ActionIn<T, T2, T3> f) { lock (l1.theLock) lock (l2.theLock) lock (l3.theLock) f(in l1.val, in l2.val, in l3.val); }
public TRES Lock<TRES>(FuncRef<T, T2, T3, TRES> f) { lock (l1.theLock) lock (l2.theLock) lock (l3.theLock) return f(ref l1.val, ref l2.val, ref l3.val); }
public TRES Lock<TRES>(FuncIn<T, T2, T3, TRES> f) { lock (l1.theLock) lock (l2.theLock) lock (l3.theLock) return f(in l1.val, in l2.val, in l3.val); }
}
public ThreeLockable<T3> Combine<T3>(Lockable<T3> l3) where T3 : new() => new ThreeLockable<T3>(this.l1, this.l2, l3);
}
public TwoLockable<T2> Combine<T2>(Lockable<T2> l2) where T2 : new() => new TwoLockable<T2>(this, l2);
}
}
With that code, I can now do this:
public readonly Lockable<Customers> Customers = new Lockable<Customers>();
and now the only way to access Customers
is via the Lock method like this:
Customers.Lock((ref Customers Customers) => {
Log(Customers.GetPhone("John"));
Customers=new Customers();
Customers.Add("Mary");
});
or to return a value I can do:
var count = Customers.Lock((ref Customers Customers) => Customers.Count());
Note that you can use the above to modify the state without locking i.e.:
var UnlockedCustomers = Customers.Lock((ref Customers Customers) => Customers);
UnlockedCustomers.Add("Dave"); // unlocked !!!
This is by design as I wanted to allow that but make it very explicit in the code (unlike forgetting to lock..), if this is not desired the locks returning values need to be removed (FuncRefs
etc.).
If I need to lock both I combine the Locks:
public readonly Lockable<Agents> Agents = new Lockable<Agents>();
Customers.Combine(Agents).Lock((ref Customers Customers, ref Agents Agents) => {
// both Agents and Customers are locked here !
});
I included versions of combining 2 and 3 Lockables. More can be added. Note that as with locks you have to always combine in the same order or risk deadlocks. It would be interesting to think of ways around this.
Lock
have variousref
andin
overloads, but no 'plain' variants? \$\endgroup\$