对于这类事情,我有一个 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;
}
}