void ProcessRtdCalls()
        {
            if (_rtdCalls.Count == 0)
            {
                return;
            }
            // First, build a dictionary of what we saw,
            // and add any new active callers.
            Dictionary <ExcelReference, TopicIdList> callerTopicMap = new Dictionary <ExcelReference, TopicIdList>();

            foreach (RtdCall call in _rtdCalls)
            {
                // find the corresponding RtdTopicInfo
                int topic;
                if (!_activeTopics.TryGetValue(call.TopicKey, out topic))
                {
                    Debug.Print("!!! Unknown Rtd Call: " + call.TopicKey);
                    continue;
                }

                // This is a call to a topic we know
                // Check if we already have an entry for this caller in the callerTopicMap....
                TopicIdList callerTopics;
                if (!callerTopicMap.TryGetValue(call.Caller, out callerTopics))
                {
                    // ... no - it's a new entry.
                    // Add the caller, and the topic map
                    callerTopics = new TopicIdList();
                    callerTopicMap[call.Caller] = callerTopics;
                }

                // Get the known callers
                ExcelReferenceSet callers = _activeTopicCallers[topic];
                if (!callers.Contains(call.Caller))
                {
                    // Previously unknown caller for this topic - add to _activeTopicCallers
                    callers.Add(call.Caller);

                    // Add the Topic to the list of topic to watch for this caller
                    TopicIdList topics;
                    if (!_activeCallerTopics.TryGetValue(call.Caller, out topics))
                    {
                        // Not seen this caller before for this topic - record for future use
                        topics = new TopicIdList();
                        _activeCallerTopics[call.Caller] = topics;
                    }
                    // This is a caller we've dealt with before
                    // This topic should not be in the list
                    // TODO: What if it is called twice from a single formula...?
                    Debug.Assert(!topics.Contains(topic));
                    topics.Add(topic);

                    // NOTE: topics might include the orphans!
                }
                // One of the known callers
                // Anyway - record that we saw it in this calc
                callerTopics.Add(topic);
            }

            // Now figure out what to clean up

            // For each caller and its topics that we saw in this calc ...
            TopicIdList orphans = new TopicIdList();

            foreach (KeyValuePair <ExcelReference, TopicIdList> callerTopics in callerTopicMap)
            {
                ExcelReference thisCalcCaller = callerTopics.Key;
                TopicIdList    thisCalcTopics = callerTopics.Value;

                // ... Check the topics in the _activeCallerTopics list for this caller.
                TopicIdList activeTopics         = _activeCallerTopics[thisCalcCaller];
                TopicIdList activeTopicsToRemove = null; // Lazy initialize
                foreach (int activeTopic in activeTopics)
                {
                    // If we've seen the topic in this calc, all is fine.
                    if (thisCalcTopics.Contains(activeTopic))
                    {
                        continue;
                    }

                    // ... Any topic not seen in this calc might be an orphan (so check if it has other callers).
                    // ... ensure that the active topic also does not have the caller in its activeCallers list any more.
                    ExcelReferenceSet activeCallers = _activeTopicCallers[activeTopic];
                    if (activeCallers.Remove(thisCalcCaller))
                    {
                        // - now check if this topic is an orphan
                        if (activeCallers.Count == 0)
                        {
                            orphans.Add(activeTopic);
                        }
                    }

                    // The activeTopic was one of the topics for thisCalcCaller, but is no longer.
                    // Should now be removed from the list of topics for this caller.
                    if (activeTopicsToRemove == null)
                    {
                        activeTopicsToRemove = new TopicIdList();
                    }
                    activeTopicsToRemove.Add(activeTopic);
                }

                if (activeTopicsToRemove != null)
                {
                    foreach (int topicToRemove in activeTopicsToRemove)
                    {
                        activeTopics.Remove(topicToRemove);
                        if (activeTopics.Count == 0)
                        {
                            // Unlikely...?  (due to how the bug works - the caller should have a new topic)
                            _activeCallerTopics.Remove(thisCalcCaller);
                        }
                    }
                }
            }

            // Clear our recording and disconnect the orphans
            _rtdCalls.Clear();
            DisconnectOrphanedTopics(orphans);
        }
        void ProcessRtdCalls()
        {
            if (_rtdCalls.Count == 0 && _rtdCompletes.Count == 0)
            {
                return;
            }
            // First, build a dictionary of what we saw,
            // and add any new active callers.
            Dictionary <ExcelReference, TopicIdList> callerTopicMap = new Dictionary <ExcelReference, TopicIdList>();

            foreach (RtdCall call in _rtdCalls)
            {
                // find the corresponding RtdTopicInfo
                int topic;
                if (!_activeTopics.TryGetValue(call.TopicKey, out topic))
                {
                    Debug.Print("!!! Unknown Rtd Call: " + call.TopicKey);
                    continue;
                }

                // This is a call to a topic we know
                // Check if we already have an entry for this caller in the callerTopicMap....
                TopicIdList callerTopics;
                if (!callerTopicMap.TryGetValue(call.Caller, out callerTopics))
                {
                    // ... no - it's a new entry.
                    // Add the caller, and the topic map
                    callerTopics = new TopicIdList();
                    callerTopicMap[call.Caller] = callerTopics;
                }

                // Get the known callers
                ExcelReferenceSet callers = _activeTopicCallers[topic];
                if (!callers.Contains(call.Caller))
                {
                    // Previously unknown caller for this topic - add to _activeTopicCallers
                    callers.Add(call.Caller);

                    // Add the Topic to the list of topic to watch for this caller
                    TopicIdList topics;
                    if (!_activeCallerTopics.TryGetValue(call.Caller, out topics))
                    {
                        // Not seen this caller before for this topic - record for future use
                        topics = new TopicIdList();
                        _activeCallerTopics[call.Caller] = topics;
                    }
                    // This is a caller we've dealt with before
                    // This topic should not be in the list
                    // TODO: What if it is called twice from a single formula...?
                    Debug.Assert(!topics.Contains(topic));
                    topics.Add(topic);

                    // NOTE: topics might include the orphans!
                }
                // One of the known callers
                // Anyway - record that we saw it in this calc
                callerTopics.Add(topic);
            }

            // Process calls that are 'complete' - we can record that we didn't see (i.e. call xlfRtd for) the topic in this call
            foreach (var call in _rtdCompletes)
            {
                // These callers were called, but not with the topics we expected (since there was no real RTD call)
                // find the corresponding RtdTopicInfo
                int topic;
                if (!_activeTopics.TryGetValue(call.TopicKey, out topic))
                {
                    Debug.Fail("!!! Unknown Rtd Call: " + call.TopicKey);
                    continue;
                }

                // This is a call to a topic we know
                // Check if we already have an entry for this caller in the callerTopicMap....
                TopicIdList callerTopics;
                if (!callerTopicMap.TryGetValue(call.Caller, out callerTopics))
                {
                    // This caller has no topics (in this calculation)
                    // Note that it was called (but we'll add no topics...)
                    // Otherwise it's fine - we've listed this as a caller to examine, but we won't put this topic in
                    callerTopics = new TopicIdList();
                    callerTopicMap[call.Caller] = callerTopics;
                }

                if (callerTopics.Contains(topic))
                {
                    Debug.Fail("!!! Inconsistent Rtd Call (RtdCalls contains the RtdComplete call): " + call.TopicKey);
                }
            }


            // Now figure out what to clean up

            // For each caller and its topics that we saw in this calc ...
            TopicIdList orphans = new TopicIdList();

            foreach (KeyValuePair <ExcelReference, TopicIdList> callerTopics in callerTopicMap)
            {
                ExcelReference thisCalcCaller = callerTopics.Key;
                TopicIdList    thisCalcTopics = callerTopics.Value;

                // ... Check the topics in the _activeCallerTopics list for this caller.
                TopicIdList activeTopics         = _activeCallerTopics[thisCalcCaller];
                TopicIdList activeTopicsToRemove = null; // Lazy initialize
                foreach (int activeTopic in activeTopics)
                {
                    // If we've seen the topic in this calc, all is fine.
                    if (thisCalcTopics.Contains(activeTopic))
                    {
                        continue;
                    }

                    // ... Any topic not seen in this calc might be an orphan (so check if it has other callers).
                    // ... ensure that the active topic also does not have the caller in its activeCallers list any more.
                    ExcelReferenceSet activeCallers = _activeTopicCallers[activeTopic];
                    if (activeCallers.Remove(thisCalcCaller))
                    {
                        // - now check if this topic is an orphan
                        if (activeCallers.Count == 0)
                        {
                            orphans.Add(activeTopic);
                        }
                    }

                    // The activeTopic was one of the topics for thisCalcCaller, but is no longer.
                    // Should now be removed from the list of topics for this caller.
                    if (activeTopicsToRemove == null)
                    {
                        activeTopicsToRemove = new TopicIdList();
                    }
                    activeTopicsToRemove.Add(activeTopic);
                }

                if (activeTopicsToRemove != null)
                {
                    foreach (int topicToRemove in activeTopicsToRemove)
                    {
                        activeTopics.Remove(topicToRemove);
                        if (activeTopics.Count == 0)
                        {
                            // This happens if we have completed the topic, and Excel might not disconnect
                            // but the topic is no longer active.
                            // I think Excel will try to Disconnect later, e.g. if we delete the formula or something.
                            _activeCallerTopics.Remove(thisCalcCaller);
                        }
                    }
                }
            }

            // Clear our recording and disconnect the orphans
            _rtdCalls.Clear();
            DisconnectOrphanedTopics(orphans);
        }