2012-01-26 13 views
9

Digamos que tiene una plantilla de texto con un número de campos que necesitan ser poblada:población plantilla eficiente

var template = "hello {$name}. you are {$age} years old. you live in {$location}" 

y un IDictionary<string,string> de los valores a sustituir:

key  | value 
=================== 
name | spender 
age  | 38 
location| UK 

La forma ingenua de poblar la plantilla podría ser algo así como:

var output = template; 
foreach(var kvp in templValues) 
{ 
    output = output.Replace(string.format("{{${0}}}", kvp.Key), kvp.Value); 
} 

Sin embargo, esto parece muy poco eficiente. ¿Hay una mejor manera?

+0

Puedo preguntar cuál era la situación que lo obligó a hacer esto en lugar de una cadena "normal". ¿Formato? –

+0

@BrankoDimitrijevic: Un montón de plantillas de correo electrónico editables por el usuario – spender

Respuesta

4

Se puede usar un Regex.Replace(), así:

var output = new Regex(@"\{\$([^}]+)\}").Replace(
    template, 
    m => templValues.ContainsKey(m.Captures[1].Value) 
     ? templValues[m.Captures[1].Value] 
     : m.Value); 

yo sepa Esto también evitará resultados inesperados si su diccionario se construye de esta manera, ya que esto podría producir "hello UK. you are 38 years old. you live in UK", así como "hello {$location}. you are 38 years old. you live in UK", ya que no lo hacen dictionarys ordenar sus claves:

key  | value 
=================== 
name | {$location} 
age  | 38 
location| UK 

Cuando realmente se desea el primer comportamiento, puede simplemente ejecutar la expresión regular varias veces.

Edit: Si el análisis de la plantilla está realmente en una parte crítica del tiempo, no hace el análisis de la plantilla allí., debería considerar utilizar el método de análisis manual Sean recomendado.

+0

no muy legibles, prefiero el método del gastador :) – vulkanino

+1

@vulkanino: El enfoque del OP podría ser más legible, pero no es seguro con respecto a las variables que contienen tokens válidos. Y si tiene un conjunto muy grande de tokens posibles, pero usa solo uno o dos de ellos en su plantilla, el ciclo sobre todos los tokens posibles es definitivamente más lento. – Nuffin

+0

Me gusta mucho esto. Se había olvidado por completo de MatchEvaluator. Bonito. – spender

-2

A riesgo de parecer tonto, usted podría escribir una función para devolver la cadena que desea:

public string CreateString(string name, string age, string location) 
{ 
    return "hello " + name + ". you are " + age + " years old. you live in " + location; 
} 

Dado que sólo puede almacenar un conjunto de valores en el diccionario, el valor de uso de una plantilla de esta manera parece disminuido.

+0

Sí. Muy tonto. Eso es completamente irreutilizable. Tratando de resistir un voto negativo. – spender

+0

No quiero ver el código de producción donde se usa este enfoque y hay varias plantillas y probablemente varios cientos de variables disponibles ... – Nuffin

+0

@Tobias: De acuerdo. Para grandes conjuntos de variables, esto sería malo. Supongo que estaba demasiado centrado en el caso de las tres variables. – JayP

4

No hay nada de malo en su enfoque, depende del contexto en que se utiliza. Por ejemplo, en un ciclo estrecho de misión crítica no es el enfoque más eficiente, pero para uso ocasional, o en una interfaz gráfica de usuario, probablemente esté bien.

Una solución más eficiente sería analizar la cadena. Por ejemplo. busque el primer { y luego el siguiente }. El texto entre ellos es la clave de búsqueda, que luego puede sustituir. A continuación, comienza con la búsqueda del personaje después del }. La ventaja de este enfoque es que si el valor que inserta tiene un token incrustado, no se reemplazará. La desventaja es que es más difícil manejar los casos extremos al analizar.

+0

+1: Creo que esto no es un problema. – vulkanino

1

utilizar una expresión regular que coincide con un especificador de campo:

var fieldRegex = new Regex(@"{\$([^}]+?)}", RegexOptions.Compiled); 

Regex explicación:

  1. un literal {
  2. un literal $ (que tiene que ser escapado)
  3. una grupo capturado () que contiene:
    1. no } caracteres
    2. uno o más de ellos +
    3. tomar la menor cantidad posible ? (captura con pereza)
  4. un literal }

coincide con esta expresión regular en contra de la plantilla, utilizando un evaluador personalizado que sustituye en el valor de campo relevante:

var template = "hello {$name}. you are {$age} years old. you live in {$location}"; 

var fieldValues = new Dictionary<string, string> 
         { 
          { "name", "spender" }, 
          { "age", "38" }, 
          { "location", "UK" }, 
         }; 

var output = fieldRegex.Replace(
    template, 
    match => fieldValues[match.Groups[1].Value]); 

Puede dividir esta lambda en un método que compruebe si el campo realmente existe, si lo desea.

0

Si usted está preocupado por el rendimiento, analizar manualmente la plantilla en una sola pasada es probablemente el más rápido se puede ir:

static string DictFormat(string template, IDictionary<string, string> dict) { 

    const string left_delimiter = "{$"; 
    int left_delimiter_len = left_delimiter.Length; 
    const string right_delimiter = "}"; 
    int right_delimiter_len = right_delimiter.Length; 

    var sb = new StringBuilder(); 

    int end = 0; 
    while (true) { 

     int start = template.IndexOf(left_delimiter, end); 
     if (start >= 0) { 
      sb.Append(template.Substring(end, start - end)); 
      start += left_delimiter_len; 
      end = template.IndexOf(right_delimiter, start); 
      if (end >= 0) { 
       string key = template.Substring(start, end - start); 
       string value; 
       if (dict.TryGetValue(key, out value)) { 
        sb.Append(value); 
        end += right_delimiter_len; 
       } 
       else 
        throw new ArgumentException(string.Format("Key not found: {0}", key), "template"); 
      } 
      else 
       throw new ArgumentException(string.Format("Key starting at {0} not properly closed.", start), "template"); 
     } 
     else { 
      sb.Append(template.Substring(end)); 
      return sb.ToString(); 
     } 

    } 

} 

utilizar de esta manera:

const string template = "hello {$name}. you are {$age} years old. you live in {$location}"; 
var dict = new Dictionary<string, string> { { "name", "spender" }, { "age", "38" }, { "location", "UK" } }; 
string result = DictFormat(template, dict); 
Cuestiones relacionadas