Nerdynosaur
Nerdynosaur

Reputation: 1888

C# Optimal way to store and output a list of strings

Currently my application have a textbox that will show 500 lines of live logging on the screen. Since the application have alot of heavy task running behind, so i try to improve this part for my application as much as i can. Open for any suggestion. Thanks.

WPF:

<TextBox Text="{Binding StatusMessage}" TextWrapping="Wrap" AcceptsReturn="True" />

Method 1:

private void OutputMessage(string msg)
{
    //Result
    //1000  Lines     108ms
    //10000 Lines    1687ms
    if(!string.IsNullOrEmpty(msg))
    {
        string msgWithTimeStamp = System.DateTime.Now.ToString() + ": \t" + msg;

        int msgLine = 0;
        if (!string.IsNullOrEmpty(StatusMessage))
        {
            //Only show 500 lines of latest messages...
            msgLine = StatusMessage.Count(m => m == '\n');
            if (msgLine > 500)
                StatusMessage = StatusMessage.Remove(0, StatusMessage.IndexOf("\n") + 1);

            StatusMessage = StatusMessage + "\n" + msgWithTimeStamp;
        }
        else
            StatusMessage = msgWithTimeStamp;
    }
}

Method 2 (Better performance):

List<string> msgList;
private void OutputMessage(string msg)
{
    //Result
    //1000  Lines     31ms
    //10000 Lines    377ms
    if (!string.IsNullOrEmpty(msg))
    {
        string msgWithTimeStamp = System.DateTime.Now.ToString() + ": \t" + msg;
        msgList.Add(msgWithTimeStamp);

        if (msgList.Count > 500)
            msgList.RemoveAt(0);

        StatusMessage = String.Join("\n", msgList);
    }
}

Upvotes: 1

Views: 559

Answers (2)

Phil Wright
Phil Wright

Reputation: 22946

Instead of using a TextBox you could use an ItemsControl and data bind its ItemsSource to an ObservableCollection. Then you just append each new message to the ObservableCollection and the ItemsControl will automatically update to show the new entry at the end of its list.

Upvotes: 4

ajawad987
ajawad987

Reputation: 4672

The biggest problem is the following line:

StatusMessage = String.Join("\n", msgList);

This means your rebuilding the entire string every time the main list of strings is updated, and it loops through the entire list, allocating new memory buffers until the buffer is large enough to fill the entire string (all 500 lines). When your message list grows to 500 lines, it means the next message will cause 500 buffer allocations just to rebuild the full string.

A better way would be to maintain a StringBuilder alongside your list of messages (in your msgList variable). StringBuilder is smart in how it allocates buffers as you modify your messages.

I wrote the following snippet, and on my machine the time it takes to add a new message to the list that already has 500 messages in it is less than a millisecond.

// Somewhere in your code, probably next to your [msgList] variable...
private StringBuilder sb = new StringBuilder();

private void OutputMessage(string msg)
{
    if (!string.IsNullOrEmpty(msg))
    {
        string msgWithTimeStamp = System.DateTime.Now.ToString() + ": \t" + msg;
        msgList.Add(msgWithTimeStamp);
        sb.AppendLine(msgWithTimeStamp);

        if (msgList.Count > 500)
        {
            sb.Remove(0, msgList[0].Length + 2); // The +2 is to account for the new line and carriage return characters.
            msgList.RemoveAt(0);
        }

        statusMessage = sb.ToString();
    }
}

The only downside to this approach is the maintenance of the same set of messages twice.

Upvotes: 3

Related Questions