I have a set of templates for emails that my app sends out. The templates have codes embedded in them that correspond to properties of my business object.
Is there a more elegant way than calling
string.Replace("{!MyProperty!}", item.MyProperty.ToString())
a zillion times? Maybe XMLTransform, regular expressions, or some other magic? I'm using C# 3.5 .
First of all, when I do this I use StringBuilder.Replace() as I have found that its performance is much better suited when working with 3 or more replacements.
Of course there are other ways of doing it, but I've found that it is not usually worth the extra effort to try other items.
You might be able to use Reflection I guess to automate the replacement, that might be the only "better" way.
public static string Translate(string pattern, object context)
{
return Regex.Replace(pattern, #"\{!(\w+)!}", match => {
string tag = match.Groups[1].Value;
if (context != null)
{
PropertyInfo prop = context.GetType().GetProperty(tag);
if (prop != null)
{
object value = prop.GetValue(context);
if (value != null)
{
return value.ToString();
}
}
}
return "";
});
}
Translate("Hello {!User!}. Welcome to {!GroupName!}!", new {
User = "John",
GroupName = "The Community"
}); // -> "Hello John. Welcome to The Community!"
https://ideone.com/D9J31c
There's a built in WebControl, System.Web.UI.WebControls.MailDefinition that does string replacements (among other things). Pity they tightly coupled it to the Smtp settings in app.config and a web control, and then made it sealed to foil inheritors.
But, it does handle a few things you'd most likely want in a mail template engine - body text from a file, html email, embedded objects, etc. Reflector shows the actual replacement is handled with a foreach loop and Regex.Replace - which seems a reasonable choice to me as well.
A quick glance through shows that if you can live with the from address being in the app.config (you can change it on the returned MailMessage afterwards), you only need the owner control for embedded resources or the BodyFileName.
If you're using ASP.NET or can live with the limitations - I'd choose MailDefinition. Otherwise, just do a foreach over a dictionary and a Regex.Replace. It's a little memory hungry, because of the repeated allocations of body - but they're short lived and shouldn't pose much of a problem.
var replacements = new Dictionary<string, object>() {
{ "Property1", obj.Property1 },
{ "Property2", obj.Property2 },
{ "Property3", obj.Property3 },
{ "Property4", obj.Property4 },
}
foreach (KeyValuePair<string, object> kvp in replacement) {
body = Regex.Replace(body, kvp.Key, kvp.Value.ToString());
}
If you really have a lot of properties, then read your body first with Regex.Match and reflect to the properties instead.
You could do it using a regex, but your regex replace differs also for each property. I would stick with the string.Replace.
Use reflection to retrieve the properties and replace it in a loop:
foreach (string property in properties)
{
string.Replace("{!"+property+"!}",ReflectionHelper.GetStringValue(item,property));
}
Just implement your ReflectionHelper.GetStringValue method and use reflection to retrieve all the properties on your item object type.
After looking at the examples previously included, I thought I'd look at the real code.
#mark-brackett You were closer than you knew.
//The guts of MailDefinition.CreateMailMessage
//from https://github.com/Microsoft/referencesource/blob/master/System.Web/UI/WebControls/MailDefinition.cs
if (replacements != null && !String.IsNullOrEmpty(body)) {
foreach (object key in replacements.Keys) {
string fromString = key as string;
string toString = replacements[key] as string;
if ((fromString == null) || (toString == null)) {
throw new ArgumentException(SR.GetString(SR.MailDefinition_InvalidReplacements));
}
// DevDiv 151177
// According to http://msdn2.microsoft.com/en-us/library/ewy2t5e0.aspx, some special
// constructs (starting with "$") are recognized in the replacement patterns. References of
// these constructs will be replaced with predefined strings in the final output. To use the
// character "$" as is in the replacement patterns, we need to replace all references of single "$"
// with "$$", because "$$" in replacement patterns are replaced with a single "$" in the
// final output.
toString = toString.Replace("$", "$$");
body = Regex.Replace(body, fromString, toString, RegexOptions.IgnoreCase);
}
}
Related
Is there any way to make Search and addToSearch faster?
I am trying to make it faster. I am not sure if regex in addtosearch can be a problem, it is really small. I am out ofideas how to optimize it further. Now i am just trying to meet word count. I wonder if there is a way to concatenate parts of name that are not empty more effectivly than i do.
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System;
namespace AutoComplete
{
public struct FullName
{
public string Name;
public string Surname;
public string Patronymic;
}
public class AutoCompleter
{
private List<string> listOfNames = new List<string>();
private static readonly Regex sWhitespace = new Regex(#"\s+");
public void AddToSearch(List<FullName> fullNames)
{
foreach (FullName i in fullNames)
{
string nameToAdd = "";
if (!string.IsNullOrWhiteSpace(i.Surname))
{
nameToAdd += sWhitespace.Replace(i.Surname, "") + " ";
}
if (!string.IsNullOrWhiteSpace(i.Name))
{
nameToAdd += sWhitespace.Replace(i.Name, "") + " ";
}
if (!string.IsNullOrWhiteSpace(i.Patronymic))
{
nameToAdd += sWhitespace.Replace(i.Patronymic, "") + " ";
}
listOfNames.Add(nameToAdd.Substring(0, nameToAdd.Length - 1));
}
}
public List<string> Search(string prefix)
{
if (prefix.Length > 100 || string.IsNullOrWhiteSpace(prefix))
{
throw new System.Exception();
}
List<string> namesWithPrefix = new List<string>();
foreach (string name in listOfNames)
{
if (IsPrefix(prefix, name))
{
namesWithPrefix.Add(name);
}
}
return namesWithPrefix;
}
private bool IsPrefix(string prefix, string stringToSearch)
{
if (stringToSearch.Length < prefix.Length)
{
return false;
}
for (int i = 0; i < prefix.Length; i++)
{
if (prefix[i] != stringToSearch[i])
{
return false
}
}
return true
}
}
}
Regular expression (Regexp) are great because of their ease-of use and flexibility but most Regexp engines are actually quite slow. This is the case for the one of C#. Moreover, strings can contain Unicode character and "\s" needs to consider all the (fancy) spaces characters included in the Unicode character set. This make Regexp search/replace much slower. If you know your input does not contain such characters (eg. ASCII), then you can write a much faster implementation. Alternatively, you can play with RegexpOptions like Compiled and CultureInvariant so to reduce a bit the run time.
The AddToSearch performs many hidden allocations. Indeed, += create a new string (because C# string are immutable and not designed to be often resized) and Replace calls does allocate new strings too. You can speed up the computation by directly replace and write the result in a preallocated buffer and simply copy the result with a Substring like you currently do.
Search is fine and it is not easy to optimize it. That being said, if listOfNames is big, then you can use multiple threads so to significantly speed up the computation. Be careful though because Add is not thread-safe. Parallel linkq may help you to do that easily (I never tested it though).
Another solution to speed up a bit the computation of Search is to start the loop of IsPrefix from prefix.Length-1. Indeed, if most string contains the beginning of the prefix, then a significant portion of the time will be spend comparing nearly equal characters. The probability that prefix[prefix.Length-1] != stringToSearch[prefix.Length-1] is higher than prefix[0] != stringToSearch[0]. Additionally, partial loop unrolling may help a bit to speed up the function if the JIT is not able to do that.
Others have already pointed out that the use of regex can be problematic. I would personally consider using str.Replace(" ", String.Empty) - if I understood the regex correctly; I normally try to avoid regex as I have a hard time reading code using regex. Note that String.Empty does not allocate a new string.
That said, I think performance could boost if you would not store the names in a List but at least order the list alpabetically. Thus you do not need to iterate all elemnts of the list but e.g. use binary search to find all elements matching a given prefix - as range within the list of names you already have.
Currently I'm working on an web-API dependent application. I have made a class with a lot of API strings like: /api/lol/static-data/{region}/v1/champion/{id}. Also I made a method:
public static String request(String type, Object[] param)
{
return "";
}
This is going to do the requesting stuff. Because it's very different with each request type how many parameters are being used, I'm using an array for this. Now the question is, is it posssible to String.Format using an array for the paramaters while the keys in the strings are not numbers? Or does anyone know how to do this in a different way?
No, string.Format only supports index-based parameter specifications.
This:
"/api/lol/static-data/{region}/v1/champion/{id}"
^^^^^^^^ ^^^^
will have to be handled using a different method, like string.Replace or a Regex.
You will need to:
Decide on the appropriate method for doing the replacements
Decide how an array with index-based values should map to the parameters, are they positional? ie. {region} is the first element of the array, {id} is the second, etc.?
Here is a simple LINQPad program that demonstrates how I would do it (though I would add a bit more error handling, maybe caching of the reflection info if this is executed a lot, some unit-tests, etc.):
void Main()
{
string input = "/api/lol/static-data/{region}/v1/champion/{id}";
string output = ReplaceArguments(input, new
{
region = "Europe",
id = 42
});
output.Dump();
}
public static string ReplaceArguments(string input, object arguments)
{
if (arguments == null || input == null)
return input;
var argumentsType = arguments.GetType();
var re = new Regex(#"\{(?<name>[^}]+)\}");
return re.Replace(input, match =>
{
var pi = argumentsType.GetProperty(match.Groups["name"].Value);
if (pi == null)
return match.Value;
return (pi.GetValue(arguments) ?? string.Empty).ToString();
});
}
I have a json object which I converted to dynamic C# object with help of this answer. It works just fine, but trouble is that this object has numerical keys. For instance,
var jsStr = "{address:{"100": {...}}}";
So I can't wirte
dynObj.address.100
And, as I know, I can't use indexers to get this object like this
dynObj.address["100"]
Please explain to me how I can get this working.
As far as I can see from the source code he resolves the properties through a private dictionary, so you have to use reflection to access the dictionary key, or modify his code a bit so that TryGetMember in DynamicJSONObject is the following (and use __numeric__ to get the key e.g. data.address.__numeric__100, and then avoid using __numeric__ as a key):
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var name = binder.Name;
//Code to check if key is of form __numeric__<number> so that numeric keys can be accessed
if (binder != null && binder.Name != null && binder.Name.StartsWith("__numeric__"))
{
name = binder.Name.Substring(11);
}
if (!_dictionary.TryGetValue(name, out result))
{
// return null to avoid exception. caller can check for null this way...
result = null;
return true;
}
var dictionary = result as IDictionary<string, object>;
if (dictionary != null)
{
result = new DynamicJsonObject(dictionary);
return true;
}
var arrayList = result as ArrayList;
if (arrayList != null && arrayList.Count > 0)
{
if (arrayList[0] is IDictionary<string, object>)
result = new List<object>(arrayList.Cast<IDictionary<string, object>>().Select(x => new DynamicJsonObject(x)));
else
result = new List<object>(arrayList.Cast<object>());
}
return true;
}
My opensource framework ImpromptuInterface has methods to call dynamic members via string name of any C# 4 dynamic object.
object tOut =Impromptu.InvokeGet(dynObj.address,"100");
I tested it with an ExpandoObject it seemed to work just fine.
An identifier must start with a
letter, underscore (_), or dollar sign
($); subsequent characters can also be
digits (0-9). Because JavaScript is
case sensitive, letters include the
characters "A" through "Z" (uppercase)
and the characters "a" through "z"
(lowercase). Starting with JavaScript
1.5, ISO 8859-1 or Unicode letters (or \uXXXX Unicode escape sequences) can
be used in identifiers.
Quoted from: http://en.wikipedia.org/wiki/JavaScript_syntax#Variables
Oh I am sorry mis understood the question, well here you go with a working example you can adjust to your needs:
<script>
var jsStr = {address:{'100': 'test'}};
var test = jsStr.address;
console.log(test);
alert(test[100]);
</script>
btw key CAN be numeric (as you see in the example in the answer), only the identifiers cannot. so you have to access just like you tried. you only have to leave away the quotes for numeric keys! and your json string will not be an object without evaluation, so in this example its strictly speaking a javascript object and not json but it doesnt matter to t he subject
I need to convert a collection of <string,string> to a single string containing all the values in the collection like KeyValueKeyValue... But How do I do this effectively?
I have done it this way at the moment:
parameters = string.Join("", requestParameters.Select(x => string.Concat(x.Key, x.Value)));
But not sure it is the best way to do it, would a string builder be better? I guess the collection will contain a max of 10 pairs.
string.Join used to not really be the best option since it only accepted string[] or object[] parameters, requiring that any select-style queries needed to be completely evaluated and put into an array first.
.NET 4.0 brought with it an overload that accepts IEnumerable<string> -- which is what you are using -- and even an overload that accepts any IEnumerable<T>. These are definitely your best bet as they are now part of the BCL.
Incidentally, cracking open the source for the first overload in Reflector shows code that follows pretty closely to what davisoa suggested:
public static string Join(string separator, IEnumerable<string> values)
{
if (values == null)
{
throw new ArgumentNullException("values");
}
if (separator == null)
{
separator = Empty;
}
using (IEnumerator<string> enumerator = values.GetEnumerator())
{
if (!enumerator.MoveNext())
{
return Empty;
}
StringBuilder builder = new StringBuilder();
if (enumerator.Current != null)
{
builder.Append(enumerator.Current);
}
while (enumerator.MoveNext())
{
builder.Append(separator);
if (enumerator.Current != null)
{
builder.Append(enumerator.Current);
}
}
return builder.ToString();
}
}
So in other words, if you were to change this code to use a StringBuilder, you'd just be rewriting what MS already wrote for you.
With such a small collection, there isn't much of a performance concern, but I would probably just use a StringBuilder to Append all of the values.
Like this:
var sb = new Text.StringBuilder;
foreach (var item in requestParameters)
{
sb.AppendFormat("{0}{1}", item.Key, item.Value);
}
var parameters = sb.ToString();
String builder would be fine. Use append to add each a string to the string builder.
Basically the only reason why concat, replace, join, string+string , etc are considered not-the-best because they all tend to destroy the current string & recreate a new one.
So when you have adding strings like upto 10-12 time it really means you will destroy & recreate a string that many times.
What is the most efficient way to parse a C# string in the form of
"(params (abc 1.3)(sdc 2.0)(www 3.05)....)"
into a struct in the form
struct Params
{
double abc,sdc,www....;
}
Thanks
EDIT
The structure always have the same parameters (same names,only doubles, known at compile time).. but the order is not granted.. only one struct at a time..
using System;
namespace ConsoleApplication1
{
class Program
{
struct Params
{
public double abc, sdc;
};
static void Main(string[] args)
{
string s = "(params (abc 1.3)(sdc 2.0))";
Params p = new Params();
object pbox = (object)p; // structs must be boxed for SetValue() to work
string[] arr = s.Substring(8).Replace(")", "").Split(new char[] { ' ', '(', }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < arr.Length; i+=2)
typeof(Params).GetField(arr[i]).SetValue(pbox, double.Parse(arr[i + 1]));
p = (Params)pbox;
Console.WriteLine("p.abc={0} p.sdc={1}", p.abc, p.sdc);
}
}
}
Note: if you used a class instead of a struct the boxing/unboxing would not be necessary.
Depending on your complete grammar you have a few options:
if it's a very simple grammar and you don't have to test for errors in it you could simply go with the below (which will be fast)
var input = "(params (abc 1.3)(sdc 2.0)(www 3.05)....)";
var tokens = input.Split('(');
var typeName = tokens[0];
//you'll need more than the type name (assembly/namespace) so I'll leave that to you
Type t = getStructFromType(typeName);
var obj = TypeDescriptor.CreateInstance(null, t, null, null);
for(var i = 1;i<tokens.Length;i++)
{
var innerTokens = tokens[i].Trim(' ', ')').Split(' ');
var fieldName = innerTokens[0];
var value = Convert.ToDouble(innerTokens[1]);
var field = t.GetField(fieldName);
field.SetValue(obj, value);
}
that simple approach however requires a well conforming string or it will misbehave.
If the grammar is a bit more complicated e.g. nested ( ) then that simple approach won't work.
you could try to use a regEx but that still requires a rather simple grammar so if you end up having a complex grammar your best choice is a real parser. Irony is easy to use since you can write it all in simple c# (some knowledge of BNF is a plus though).
Do you need to support multiple structs ? In other words, does this need to be dynamic; or do you know the struct definition at compile time ?
Parsing the string with a regex would be the obvious choice.
Here is a regex, that will parse your string format:
private static readonly Regex regParser = new Regex(#"^\(params\s(\((?<name>[a-zA-Z]+)\s(?<value>[\d\.]+)\))+\)$", RegexOptions.Compiled);
Running that regex on a string will give you two groups named "name" and "value". The Captures property of each group will contain the names and values.
If the struct type is unknown at compile time, then you will need to use reflection to fill in the fields.
If you mean to generate the struct definition at runtime, you will need to use Reflection to emit the type; or you will need to generate the source code.
Which part are you having trouble with ?
A regex can do the job for you:
public Dictionary<string, double> ParseString(string input){
var dict = new Dictionary<string, double>();
try
{
var re = new Regex(#"(?:\(params\s)?(?:\((?<n>[^\s]+)\s(?<v>[^\)]+)\))");
foreach (Match m in re.Matches(input))
dict.Add(m.Groups["n"].Value, double.Parse(m.Groups["v"].Value));
}
catch
{
throw new Exception("Invalid format!");
}
return dict;
}
use it like:
string str = "(params (abc 1.3)(sdc 2.0)(www 3.05))";
var parsed = ParseString(str);
// parsed["abc"] would now return 1.3
That might fit better than creating a lot of different structs for every possible input string, and using reflection for filling them. I dont think that is worth the effort.
Furthermore I assumed the input string is always in exactly the format you posted.
You might consider performing just enough string manipulation to make the input look like standard command line arguments then use an off-the-shelf command line argument parser like NDesk.Options to populate the Params object. You give up some efficiency but you make it up in maintainability.
public Params Parse(string input)
{
var #params = new Params();
var argv = ConvertToArgv(input);
new NDesk.Options.OptionSet
{
{"abc=", v => Double.TryParse(v, out #params.abc)},
{"sdc=", v => Double.TryParse(v, out #params.sdc)},
{"www=", v => Double.TryParse(v, out #params.www)}
}
.Parse(argv);
return #params;
}
private string[] ConvertToArgv(string input)
{
return input
.Replace('(', '-')
.Split(new[] {')', ' '});
}
Do you want to build a data representation of your defined syntax?
If you are looking for easily maintainability, without having to write long RegEx statements you could build your own Lexer parser. here is a prior discussion on SO with good links in the answers as well to help you
Poor man's "lexer" for C#
I would just do a basic recursive-descent parser. It may be more general than you want, but nothing else will be much faster.
Here's an out-of-the-box approach:
convert () to {} and [SPACE] to ":", then use System.Web.Script.Serialization.JavaScriptSerializer.Deserialize
string s = "(params (abc 1.3)(sdc 2.0))"
.Replace(" ", ":")
.Replace("(", "{")
.Replace(")","}");
return new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize(s);