1
\$\begingroup\$

I have a Class with a List of objects, and those objects contain a list which has lists to objects in the first class, and I'm trying to quickly index them. Simplified:

class Person
{
    public IEnumberable<HistoricalEvent> Events
    {
        get
        {
            return World.HistoricalEvents.Values.Where(x=>x.PeopleInvolved.Contains(this));
        }
    }
}

Also

class HistoricalEvent
{
    public virtual IEnumerable<HistoricalFigure> PeopleInvolved
    {
        get { return Enumerable.Empty<People>(); }
    }
...
}

and a subclass

class HistoricalEventSubclass : HistoricalEvent
{
    Person person1 = new Person("Alice");
    Person person2 = new Person("Bob");        

    public override IEnumerable<Person> PeopleInvolved
    {
        get
        {
            yield return person1;
            yield return person2;
        }
    }       
    ...
}

The Events class is called from a method using: frm.grpHistoricalFigureEvents.FillListboxWith(frm.lstHistoricalFigureEvents, Events);

    internal static void FillListboxWith(this GroupBox groupbox, ListBox listbox, IEnumerable<object> objects)
    {
        if (objects == null || !objects.Any())
        {
            groupbox.Visible = false;
            return;
        }
        groupbox.Visible = true;
        listbox.BeginUpdate();
        listbox.Items.Clear();
        listbox.Items.AddRange(objects.ToArray());
        listbox.EndUpdate();
        listbox.SelectedIndex = 0;
        var title = groupbox.Text.Split('(')[0].Trim();
        groupbox.Text = string.Format("{0} ({1})", title, listbox.Items.Count);
    }

The problem is that calling Events takes a long time (as World.HistoricalEvents can be a very big List). Is there a more time-efficient way of doing this?

Part of the slowdown is that Events is converted ToArray() and shoved into a listbox, and so I'll also be pursuing an alternative such as a listbox which can handle an IEnumberable directly.

\$\endgroup\$
  • 1
    \$\begingroup\$ If there is more code, please provide that. Unlike SO, we don't work with simplified code. \$\endgroup\$ – Jamal Mar 17 '15 at 16:56
  • \$\begingroup\$ You should profile it to see where the real slowdowns are, but my gut tells me a big win could be had by implementing Equals and GetHashCode on Person and changing PeopleInvoled to be ISet<T>. Then, use a HashSet<T> for the underlying implementation. This makes more sense from a domain perspective, as a person's involvement with an event is binary - it makes no sense that they would appear twice. \$\endgroup\$ – Dan Lyons Mar 17 '15 at 17:11
  • 1
    \$\begingroup\$ You are correct that ToArray() is a likely slowdown. Ditto if you use ToList(). If you have 100K events, your code is held up until all 100K events are collected, despite the use of yield. Consider using a BindSource.DataSource, which accepts an IEnumerable collection. \$\endgroup\$ – Rick Davin Mar 17 '15 at 17:32
  • \$\begingroup\$ @Jamal, Added the last step in the chain of this, there is a ton more code and I don't want to get bogged down in unrelated issues otherwise though. \$\endgroup\$ – Mason11987 Mar 17 '15 at 20:27
  • \$\begingroup\$ @RickDavin, as far as I can tell a BindingSource.DataSource isn't polymorphic, and the Events Method returns a set of Events which may be of many subclasses, only one was shown, so it doesn't work in those cases. Is there an alternative that is polymorphic or am I using it wrong? \$\endgroup\$ – Mason11987 Mar 17 '15 at 20:27
2
\$\begingroup\$

How do you load the data for events and people? If you can denormalise your data so that a person has a materialised Set/List of events they are involved in I suspect you would gain a lot of performance (at the cost of a bit of memory).

Here is some pseudo code to explain what I mean:

var personStore = new Dictionary<string, Person>();
var eventStore = new Dictionary<string, Event>();
foreach (var eventData in eventSource)
{
  var event = new Event(eventData);
  eventStore[eventData.Name] = event;

  foreach (var personData in eventData)
  {
    // Is this the first time we've seen this person?
    if (!personStore.HasKey(personData.Name)
    {
      personStore[personData.Name] = new Person(personData);
    }

    var person = personStore[personData.Name];

    // This creates the bidirectional mapping which makes lookups
    // in either direction fast.
    event.PeopleInvolved.Add(person);
    person.Events.Add(event);
  }  
}
\$\endgroup\$
  • \$\begingroup\$ Great question, that is essentially the set up I had prior to attempting this alternative. If I couldn't find a good enough improvement I was planning on rolling back to this alternative. The problem is that each of the event subclasses handled associated people differently and so this option allowed me to drop a huge amount of custom association code in 30+ classes, which was a big win. Lots of balancing factors. \$\endgroup\$ – Mason11987 Mar 18 '15 at 19:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.