У меня есть класс RecursionChecker для такого рода вещей. Настоящим я отказываюсь от авторских прав на код ниже.
Он жалуется, если слишком часто выполняет проверку целевого объекта. Это не конец всему; петли могут вызвать ложные срабатывания, например. Этого можно избежать, сделав еще один вызов после опасного кода, сообщив контролеру, что он может уменьшить свой вызов recurse для целевого объекта. Это все еще не будет пуленепробиваемым.
Чтобы использовать это, я просто звоню
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;
}
}