私はこのようなことのためにRecursionCheckerクラスを持っています。私はここで、以下のコードに著作権を放棄します。
ターゲットオブジェクトのチェックがあまりにも頻繁に行われると、それは文句を言います。それはすべてではありません。例えば、ループは偽陽性を引き起こす可能性があります。危険なコードの後に別の呼び出しをして、それがターゲットオブジェクトの反復呼び出しを減らすことができることをチェッカーに伝えることを避けることができます。それでも防弾ではありません。
それを使用するには、私はちょうど呼んここ
public void DangerousMethod() {
RecursionChecker.Check(someTargetObjectThatWillBeTheSameIfWeReturnHereViaRecursion);
// recursion-risky code here.
}
はRecursionCheckerクラスです:
/// <summary>If you use this class frequently from multiple threads, expect a lot of blocking. In that case,
/// might want to make this a non-static class and have an instance per thread.</summary>
public static class RecursionChecker
{
#if DEBUG
private static HashSet<ReentrancyInfo> ReentrancyNotes = new HashSet<ReentrancyInfo>();
private static object LockObject { get; set; } = new object();
private static void CleanUp(HashSet<ReentrancyInfo> notes) {
List<ReentrancyInfo> deadOrStale = notes.Where(info => info.IsDeadOrStale()).ToList();
foreach (ReentrancyInfo killMe in deadOrStale) {
notes.Remove(killMe);
}
}
#endif
public static void Check(object target, int maxOK = 10, int staleMilliseconds = 1000)
{
#if DEBUG
lock (LockObject) {
HashSet<ReentrancyInfo> notes = RecursionChecker.ReentrancyNotes;
foreach (ReentrancyInfo note in notes) {
if (note.HandlePotentiallyRentrantCall(target, maxOK)) {
break;
}
}
ReentrancyInfo newNote = new ReentrancyInfo(target, staleMilliseconds);
newNote.HandlePotentiallyRentrantCall(target, maxOK);
RecursionChecker.CleanUp(notes);
notes.Add(newNote);
}
#endif
}
}
ヘルパークラス以下:私はちょうどデジャヴのこの奇妙な感じがした
internal class ReentrancyInfo
{
public WeakReference<object> ReentrantObject { get; set;}
public object GetReentrantObject() {
return this.ReentrantObject?.TryGetTarget();
}
public DateTime LastCall { get; set;}
public int StaleMilliseconds { get; set;}
public int ReentrancyCount { get; set;}
public bool IsDeadOrStale() {
bool r = false;
if (this.LastCall.MillisecondsBeforeNow() > this.StaleMilliseconds) {
r = true;
} else if (this.GetReentrantObject() == null) {
r = true;
}
return r;
}
public ReentrancyInfo(object reentrantObject, int staleMilliseconds = 1000)
{
this.ReentrantObject = new WeakReference<object>(reentrantObject);
this.StaleMilliseconds = staleMilliseconds;
this.LastCall = DateTime.Now;
}
public bool HandlePotentiallyRentrantCall(object target, int maxOK) {
bool r = false;
object myTarget = this.GetReentrantObject();
if (target.DoesEqual(myTarget)) {
DateTime last = this.LastCall;
int ms = last.MillisecondsBeforeNow();
if (ms > this.StaleMilliseconds) {
this.ReentrancyCount = 1;
}
else {
if (this.ReentrancyCount == maxOK) {
throw new Exception("Probable infinite recursion");
}
this.ReentrancyCount++;
}
}
this.LastCall = DateTime.Now;
return r;
}
}
public static class DateTimeAdditions
{
public static int MillisecondsBeforeNow(this DateTime time) {
DateTime now = DateTime.Now;
TimeSpan elapsed = now.Subtract(time);
int r;
double totalMS = elapsed.TotalMilliseconds;
if (totalMS > int.MaxValue) {
r = int.MaxValue;
} else {
r = (int)totalMS;
}
return r;
}
}
public static class WeakReferenceAdditions {
/// <summary> returns null if target is not available. </summary>
public static TTarget TryGetTarget<TTarget> (this WeakReference<TTarget> reference) where TTarget: class
{
TTarget r = null;
if (reference != null) {
reference.TryGetTarget(out r);
}
return r;
}
}
それは面白いです。私は変わったことに気付かなかった。私がこれらのうちの1つを持っていた最後の時は、プロパティ/メンバゲッターをミスタイプし、無限の再帰呼び出しを得たときでした(そして、それを捕まえてデバッグすることができました)。 +1は実際に最新のドキュメントを読むためのものです。 –