Posted at

LazyAction

More than 1 year has passed since last update.

最近Singleton生成以外にもLazy使う事が増えた(使い方を覚えた)のだけど、FuncじゃなくてActionなLazy欲しいなぁって事が結構あって、Lazyってどういう実装なんだろうっていう事を調べるついでにLazyActionクラスを作ってみた。

Genericを伸ばすためにparams objectにキャストしているのがいけてない感じなのだけど他に楽に書く方法を思いつかなかった。

もっとこういう風に実装した方が良いよとか、いやそれすでに.NET Frameworkにあるから、とかあったら教えててください。


public class LazyAction
{
private class ExceptionHolder
{
private readonly ExceptionDispatchInfo dispatchInfo;

public ExceptionHolder(Exception exception)
{
dispatchInfo = ExceptionDispatchInfo.Capture(exception);
}

public ExceptionDispatchInfo DispatchInfo => dispatchInfo;
}

private static readonly object ALREADY_INVOKED_SENTINEL = new object();
private Action<object[]> _lazyAction;
private object _threaSafeObjet;
private object _box = null;
public bool Invoked => _box != null;

private LazyAction()
{
_threaSafeObjet = new object();
}

public LazyAction(Action lazyAction) : this()
{
_lazyAction = x=> lazyAction();
}

public LazyAction(Action<object[]> lazyAction) : this()
{
_lazyAction = lazyAction;
}

public void Invoke()
{
Invoke(null);
}
internal void Invoke(params object[] args)
{
if (_box is ExceptionHolder)
{
var holder = _box as ExceptionHolder;
holder.DispatchInfo.Throw();
}
else if (_box == (object)ALREADY_INVOKED_SENTINEL)
{
return;
}

object threadSafeObj = Volatile.Read(ref _threaSafeObjet);
var lockToken = false;
try
{
if(threadSafeObj != (object)ALREADY_INVOKED_SENTINEL)
Monitor.Enter(threadSafeObj, ref lockToken);

if (!Invoked)
{
var action = _lazyAction;
_lazyAction = null;
try
{
action?.Invoke(args);
Volatile.Write(ref _box, ALREADY_INVOKED_SENTINEL);
}
catch (Exception e)
{
_box = new ExceptionHolder(e);
throw;
}
Volatile.Write(ref _threaSafeObjet, ALREADY_INVOKED_SENTINEL);
}
}
finally
{
if(lockToken)
Monitor.Exit(threadSafeObj);
}

}
}

public class LazyAction<T> : LazyAction
{
public LazyAction(Action<T> lazyAction) : base(x => lazyAction.Invoke((T)x[0])){ }

public void Invoke(T param)
{
base.Invoke(param);
}
}

public class LazyAction<T1, T2> : LazyAction
{
public LazyAction(Action<T1, T2> lazyAction) : base(x => lazyAction.Invoke((T1)x[0], (T2)x[1])) { }

public void Invoke(T1 p1, T2 p2)
{
base.Invoke(p1, p2);
}
}

public class LazyAction<T1, T2, T3> : LazyAction
{
public LazyAction(Action<T1, T2, T3> lazyAction) : base(x => lazyAction.Invoke((T1)x[0], (T2)x[1], (T3)x[2])) { }

public void Invoke(T1 p1, T2 p2, T3 p3)
{
base.Invoke(p1, p2, p3);
}
}

Generic 分は途中で書くの飽きた。