2008-12-18 10 views
7

quiero en el buen sentido de rendimiento (espero) sustituir un parámetro llamado en mi cadena a un parámetro con nombre de código, ejemplo, mi cadena:C# parámetros a una cadena que reemplace al parámetro nombre valora

"Hi {name}, do you like milk?" 

¿Cómo podría reemplazar el {nombre} por código, expresiones regulares? ¿Demasiado caro? ¿De qué manera lo recomiendas?

¿Cómo en el ejemplo NHibernates HQL para reemplazar: my_param al valor definido por el usuario? O en ASP.NET (MVC) Enrutamiento que me gusta mejor, "{controller}/{action}", new {controller = "Hello", ...}?

Respuesta

20

¿Has confirmado que las expresiones regulares son demasiado caras?

El costo de las expresiones regulares es muy exagerado. Para un patrón tan simple, el rendimiento será bastante bueno, probablemente solo un poco menos bueno que el de búsqueda y reemplazo directos, de hecho. Además, ¿ha experimentado con el indicador Compiled al construir la expresión regular?

Dicho esto, ¿no puedes usar la forma más sencilla, es decir, Replace?

string varname = "name"; 
string pattern = "{" + varname + "}"; 
Console.WriteLine("Hi {name}".Replace(pattern, "Mike")); 
+1

+1 por hacer las cosas de la manera más simple :) –

+1

(Aunque te refieres a Reemplazar (patrón, "Mike")) –

+5

@Jon: Gracias. ¿Por qué usar un compilador cuando tienes un Jon? ;-) –

0

Una expresión regular compilada podría ser el truco, especialmente si hay muchos tokens que reemplazar. Si solo hay un puñado de ellos y el rendimiento es clave, simplemente encontraría el token por índice y lo sustituiría por las funciones de cadena. Lo creas o no, esto será más rápido que una expresión regular.

13

expresión regular es sin duda una opción viable, especialmente con un MatchEvaluator:

Regex re = new Regex(@"\{(\w*?)\}", RegexOptions.Compiled); // store this... 

    string input = "Hi {name}, do you like {food}?"; 

    Dictionary<string, string> vals = new Dictionary<string, string>(); 
    vals.Add("name", "Fred"); 
    vals.Add("food", "milk"); 

    string q = re.Replace(input, delegate(Match match) 
    { 
     string key = match.Groups[1].Value; 
     return vals[key]; 
    }); 
+0

Maldita sea ... ¿Cómo conseguiste esos 18 minutos antes que yo? –

+0

Si tiene .NET 3.5, puede eliminar la palabra clave delegar. delegado (Coincide con) puede ser coincidencia => –

+0

@scalvert - para ser exactos, es decir C# 3.0, no .NET 3.5; funcionaría también para .NET 2.0 con C# 3.0. –

3

Ahora bien, si usted tiene le reemplazos en un diccionario, como esto:

var replacements = new Dictionary<string, string>(); 
    replacements["name"] = "Mike"; 
    replacements["age"]= "20"; 

entonces la expresión regular se vuelve bastante simple:

Regex regex = new Regex(@"\{(?<key>\w+)\}"); 
    string formattext = "{name} is {age} years old"; 
    string newStr = regex.Replace(formattext, 
      match=>replacements[match.Groups[1].Captures[0].Value]); 
+1

+1 por brevedad - de todas las soluciones aportadas, este es mi favorito personal :-) –

+0

odiar a las expresiones regulares, pero esto es bastante agradable. –

0

Pruebe usar StringTemplate. Es mucho más poderoso que eso, pero hace el trabajo impecable.

1

¿Qué tal

stringVar = "Hello, {0}. How are you doing?"; 
arg1 = "John"; // or args[0] 
String.Format(stringVar, arg1) 

Usted puede incluso tener varios argumentos, simplemente incrementar el método {x} y añadir otro parámetro para el formato(). No estoy seguro de los diferentes, pero tanto "cadena" y "Cadena" tienen este método.

+1

necesita parámetros con nombre, no ordenados. –

2

Después de pensar en esto, me di cuenta de lo que realmente deseaba, era que String.Format() tomaría un IDictionary como argumento, y que las plantillas podrían escribirse usando nombres en lugar de índices.

Para sustituciones de cadenas con muchas claves/valores posibles, los números de índice resultan en plantillas de cadena ilegibles, y en algunos casos, es posible que ni siquiera sepa qué elementos van a tener qué número, así que se me ocurrió lo siguiente extensión:

https://gist.github.com/896724

Básicamente esto le permite utilizar las plantillas de cuerda con los nombres en lugar de números, y un diccionario en lugar de una matriz, y le permite tener todas las otras buenas características de cadena.Format(), que permite el uso de un IFormatProvider personalizado, si es necesario, y permite el uso de toda la sintaxis de formateo habitual: precisión, longitud, etc.

El example proporcionado en el material de referencia para String.Format es un gran ejemplo de cómo las plantillas con muchos elementos numerados vuelven completamente ilegible - portar ese ejemplo para usar este nuevo método de extensión, se obtiene algo como esto:

var replacements = new Dictionary<String, object>() 
         { 
          { "date1", new DateTime(2009, 7, 1) }, 
          { "hiTime", new TimeSpan(14, 17, 32) }, 
          { "hiTemp", 62.1m }, 
          { "loTime", new TimeSpan(3, 16, 10) }, 
          { "loTemp", 54.8m } 
         }; 

var template = 
    "Temperature on {date1:d}:\n{hiTime,11}: {hiTemp} degrees (hi)\n{loTime,11}: {loTemp} degrees (lo)"; 

var result = template.Subtitute(replacements); 

como alguien ha señalado, si lo que estás escribiendo necesita estar muy optimizado, no use algo como esto: si tiene que formatear millones de cadenas de esta manera, en un bucle, la sobrecarga de memoria y rendimiento podría ser significativa.

Por otro lado, si le preocupa escribir código legible y mantenible, y si está haciendo, por ejemplo, un conjunto de operaciones de base de datos, en el gran esquema de cosas, esta función no agregará ningún gastos generales.

...

Para mayor comodidad, Yo intento de añadir un método que acepta un objeto anónimo en lugar de un diccionario:

public static String Substitute(this String template, object obj) 
{ 
    return Substitute(
     template, 
     obj.GetType().GetProperties().ToDictionary(p => p.Name, p => p.GetValue(obj, null)) 
    ); 
} 

Por alguna razón, esto no funciona - que pasa un objeto anónimo como new { name: "value" } a ese método de extensión da un mensaje de error en tiempo de compilación diciendo que la mejor coincidencia fue la versión IDictionary de ese método. No estoy seguro de cómo solucionar eso. (¿alguien?)

+0

¿alguna vez descubrió el tipo anónimo para esto? Eso sería genial. –

+0

No recuerdo, eso fue hace mucho tiempo, y actualmente no trabajo con ASP.NET ... Miré el código de entonces, y entró en producción con el mismo aspecto: recuerdo vagamente el problema de alguna manera no con este código, sino con el código del consumidor. Esa es la mejor pista que puedo darte. –

0

o intente esto con Linq si tiene todos sus valores de reemplazo almacenados en un obj del diccionario.

Por ejemplo:

Dictionary<string,string> dict = new Dictionary<string,string>(); 
dict.add("replace1","newVal1"); 
dict.add("replace2","newVal2"); 
dict.add("replace3","newVal3"); 

var newstr = dict.Aggregate(str, (current, value) => current.Replace(value.Key, value.Value)); 

dict es su búsqueda, reemplazar pares definidos objeto Dictionary. str es la cadena con la que debe reemplazar.

0

Me gustaría ir por la solución mindplay.dk ... Funciona bastante bien.

Y, con una ligera modificación, es compatible con las plantillas de las plantillas, como "Hola {Nombre}, ¿te gusta {0}?", Reemplazando {nombre}, pero conservando {0}:

en la fuente dada (https://gist.github.com/896724), reemplazar como sigue:

 var format = Pattern.Replace(
      template, 
      match => 
       { 
        var name = match.Groups[1].Captures[0].Value; 

        if (!int.TryParse(name, out parsedInt)) 
        { 
         if (!map.ContainsKey(name)) 
         { 
          map[name] = map.Count; 
          list.Add(dictionary.ContainsKey(name) ? dictionary[name] : null); 
         } 

         return "{" + map[name] + match.Groups[2].Captures[0].Value + "}"; 
        } 
        else return "{{" + name + "}}"; 
       } 
      ); 

Además, es compatible con una longitud ({nombre, 30}), así como un formatspecifier, o una combinación de ambos.

Cuestiones relacionadas