9
\$\begingroup\$

I have the following function to replace all the occurrence of a string that matches certain token.

public string ReplaceTokenBySample(string StingValue)
{
    List<Token> tokens = GetTokenList();
    foreach (var token in tokens)
    {
        StingValue = StingValue.Replace(token.Token, token.SampleValue);
    }
    return StingValue;
}

GetTokenList(); will return

Token                   SampleValue 
##Username##            John Doe    
##UserEmail##           john.doe@domain.com 
##UserFirstName##       John    
##UserLastName##        Doe

How can I optimize this code?

Full console app code is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Rextester
{
    public class Token
    {
        public string TokenValue { get; set; }
        public string SampleValue { get; set; }
    }
    public class Program
    {
        public List<Token>GetTokenList()
        {
            List<Token> tokens = new List<Token>
            {
                new Token(){ TokenValue = "##Username##", SampleValue="John Doe" },
                new Token(){ TokenValue = "##UserEmail##", SampleValue="john.doe@domain.com " },
                new Token(){ TokenValue = "##UserFirstName##", SampleValue="John" },
                new Token(){ TokenValue = "##UserLastName##", SampleValue="Doe"}
            };
            return tokens;
        }

        public string ReplaceTokenBySample(string StringValue)
        {
            List<Token> tokens = GetTokenList();
            foreach (var token in tokens)
            {
                StringValue = StringValue.Replace(token.TokenValue, token.SampleValue);
            }
            return StringValue;
        }

        public static void Main(string[] args)
        {
            Program obj = new Program();
            string StringValue ="Hello ##Username##! I have emailed you at ##UserEmail##. ##UserFirstName## ##UserLastName## how is you days going on. Have a good day ##Username##. ";
            Console.WriteLine(obj.ReplaceTokenBySample(StringValue));
        }
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ What's the point of this token system if ReplaceTokenBySample doesn't allow you to pass in your own tokens? \$\endgroup\$ – Pieter Witvoet Jul 12 '18 at 12:47
  • \$\begingroup\$ @PieterWitvoet token and sample value will be stored on database. And here StingValue will be replace by Mail Template. User are allowed to create any number of token.. \$\endgroup\$ – Kiran Shahi Jul 12 '18 at 14:58
  • 1
    \$\begingroup\$ That's not what the code you have shown is doing, however. If you're planning to modify GetTokenList, then ReplaceTokenBySample is limited to always use whatever tokens are returned by GetTokenList. If, instead, you pass tokens in as an argument, then you can reuse the replacement logic in different contexts. \$\endgroup\$ – Pieter Witvoet Jul 13 '18 at 8:18
10
\$\begingroup\$

Loops can sometimes be so ugly. You can get rid of them completely and use Regex instead. It'll replace the placeholders one by one without scanning the string from the beginning each time. Just put your placeholder and the corresponding values in a dictionary and let Regex.Replace do the job.

var replacements = new Dictionary<string, string>
{
    ["Username"] = "John Doe",
    ["UserEmail"] = "john.doe@domain.com",
    ["UserFirstName"] = "John",
    ["UserLastName"] = "Doe"
};

var value = "Hello ##Username##! I have emailed you at ##UserEmail##. ##UserFirstName## ##UserLastName## how is you days going on. Have a good day ##Username##. ";

var pattern = $"##(?<placeholder>{string.Join("|", replacements.Keys)})##";
var result = Regex.Replace(value, pattern, m => replacements[m.Groups["placeholder"].Value], RegexOptions.ExplicitCapture);
\$\endgroup\$
8
  • 1
    \$\begingroup\$ I always forget regex...this should perform pretty well and it's also really readable!!! \$\endgroup\$ – Adriano Repetti Jul 13 '18 at 6:59
  • \$\begingroup\$ Hi @t3chb0t would it be costly to convert list to dictionary? Because list is return from database. \$\endgroup\$ – Kiran Shahi Jul 13 '18 at 8:38
  • \$\begingroup\$ @KiranShahi there is no general answer to this question. It depends on many factors: how long is the list? how long are the strings? how many of them you are processing per second/minute/hour? It doesn't have to be a dictionary. You can look the values up in the list with .Single but agian... it depends on many many different factors. \$\endgroup\$ – t3chb0t Jul 13 '18 at 8:42
  • \$\begingroup\$ @t3chb0t is there any way to use the answer you have mention with list? Actually I don't want loop as well as type conversion \$\endgroup\$ – Kiran Shahi Jul 13 '18 at 8:44
  • 1
    \$\begingroup\$ @KiranShahi exactly... when it's working then you've implemented it correctly ;-) \$\endgroup\$ – t3chb0t Jul 13 '18 at 9:05
13
\$\begingroup\$

If this is your real use-case then I'd not go beyond the obvious optimizations. In this case you're creating a new string for each replacement, you can use StringBuilder.Replace() instead:

public string ReplaceTokenBySample(string value)
{
    var result = new StringBuilder(value);
    GetTokenList().ForEach(x => result.Replace(x.TokenValue, x.SampleValue));

    return result.ToString();
}

If instead of List<Token> you have IEnumerable<Token> (or if you want to avoid List<T>.ForEach()) then you can use:

public string ReplaceTokenBySample(string value)
{
    GetTokenList().Aggregate(new StringBuilder(value),
        (result, item) => result.Replace(item.TokenValue, item.SampleValue));

    return result.ToString();
}

Few things to consider:

  • You're performing ordinal case-sensitive comparison, it seems to be appropriate to replace placeholders with their actual value and it has the benefit to be reasonably fast. If this is not the case then you should definitely go with t3schb0t's answer (which handles this properly and it's incredibly more readable.)
  • Parameters should be camelCase.
  • It's usually confusing to overwrite parameters value with something else (especially for non trivial code), compiler is able to optimize your code to get the best of it even when using a local variable.

Few more notes about overall design: you do not validate your parameters anywhere, if they're only used internally to your assembly then mark them internal or private (as appropriate) and ASSERT about their content (for example: why do you declare Token as public?)

Names sound little bit strange, you do not need to repeat the type in the name: TokenSample and TokenValue inside the Token class (and StringValue for a value of type String).

If you intend to fill Token programmatically then adding a Token(string, string) ctor may help to keep your code shorter.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you for you answer @AdrianoRepetti I have find answer by t3chb0t more suitable :) Thank you for your time. \$\endgroup\$ – Kiran Shahi Jul 13 '18 at 9:03
  • \$\begingroup\$ @KiranShahi you welcome; unless you strictly need ordinal comparison then t3's answer should perform really well and it's much more readable. \$\endgroup\$ – Adriano Repetti Jul 13 '18 at 14:55

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.