Is it possible to convert string
"value > 5 && value <= 10"
to if statement?
if (value > 5 && value <= 10)
{
//do something
}
I have conditions stored as strings in DB, so it must be a dynamic conversion
Instead you can treat it as a javascript line and can get this done using Windows Script Engines, provided value is a real value instead of variable name.
if(ScriptEngine.Eval("jscript", "value > 5 && value <= 10"))
{
//Your logic
}
Or if its a variable then you can build a JS function like below to accomplish this:
using (ScriptEngine engine = new ScriptEngine("jscript"))
{
string JSfunction = "MyFunc(value){return " + "value > 5 && value <= 10" + "}";
ParsedScript parsed = engine.Parse(JSfunction);
if(parsed.CallMethod("MyFunc", 3))
{
// Your Logic
}
}
Use Compute method of System.Data.DataTable:
static class ExpressionHelper
{
private static readonly DataTable dt = new DataTable();
private static readonly Dictionary<string, string> expressionCache = new Dictionary<string, string>();
private static readonly Dictionary<string, object> resultCache = new Dictionary<string, object>();
// to be amended with necessary transforms
private static readonly (string old, string #new)[] tokens = new[] { ("&&", "AND"), ("||", "OR") };
public static T Compute<T>(this string expression, params (string name, object value)[] arguments) =>
(T)Convert.ChangeType(expression.Transform().GetResult(arguments), typeof(T));
private static object GetResult(this string expression, params (string name, object value)[] arguments)
{
foreach (var arg in arguments)
expression = expression.Replace(arg.name, arg.value.ToString());
if (resultCache.TryGetValue(expression, out var result))
return result;
return resultCache[expression] = dt.Compute(expression, string.Empty);
}
private static string Transform(this string expression)
{
if (expressionCache.TryGetValue(expression, out var result))
return result;
result = expression;
foreach (var t in tokens)
result = result.Replace(t.old, t.#new);
return expressionCache[expression] = result;
}
}
Usage
var expr = "value > 5 && value <= 10";
var a1 = expr.Compute<bool>(("value", 5)); // false
var a2 = expr.Compute<bool>(("value", 7)); // true
var a3 = expr.Compute<bool>(("value", 11)); // false
You could use Linq.Expression to build up the expression tree, the one you provided:
"value > 5 && value <= 10"
var val = Expression.Parameter(typeof(int), "x");
var body = Expression.And(
Expression.MakeBinary(ExpressionType.GreaterThan, val, Expression.Constant(5)),
Expression.MakeBinary(ExpressionType.LessThanOrEqual, val, Expression.Constant(10)));
var lambda = Expression.Lambda<Func<int, bool>>(exp, val);
bool b = lambda.Compile().Invoke(6); //true
bool b = lambda.Compile().Invoke(11); //false
This is just an example to get some idea, still you need a smart way to parse and build the tree.
I am afraid that you will have to create the simple parser for that.
You can try use something like FParsec. This is an F# parser library. I am not aware of such code in C#
Related
So I am unsure how to word this question properly.
If you look below lets say I have a list of options (PG , PA, PM, TK, TD) that a customer has ordered. Now lets say I have some expression I need evaluate against the customers ordered options such as: PA OR PB where evaluates to customers list of options contains either option PA or PB. Seems simple enough but these expressions could grow quite a bit. Below in the code are some good examples of what I would like to accomplish.
I by no means claim to have knowledge about string parsing and comparing logical operations. I am looking for some general direction or what my options are. I saw some things about dynamic linq, rule engines, expression trees, etc. Its just a whole lot to absorb and am looking for some direction on which would accomplish what I want?
I am open to just about any approach. Any answers are appreciated!
Code:
class Program
{
static void Main(string[] args)
{
//Reprsents what the customer selected
List<string> CustomerSelectedOptins = new List<string> { "PG", "PA", "PM", "TK", "TD" };
string LogicOperation = "PA OR (PB AND PC)"; //Evaluates true since customer list contains PA
string LogicOperation2 = "PF OR (NOT PB AND PM)"; //Evaluates true since customer list does not contain PB but contain PM
string LogicOperation3 = "NOT PG AND NOT(PL AND TZ)"; //Evaluates false since the customer does have PG selected
}
}
There are a few different approaches you can take. One common one is to replace the option checks with true or false and then use one of the built-in expression evaluators to evaluate the resulting expression. Note: XOR is not available for these.
public static class Evaluator {
static Regex wordRE = new Regex(#"[A-Z]+", RegexOptions.Compiled);
static HashSet<string> Operators = new[] { "AND", "OR", "NOT" }.ToHashSet(StringComparer.OrdinalIgnoreCase);
public static bool Evaluate(this List<string> options, string op) {
var opListOfOptions = wordRE.Matches(op).Select(m => m.Value).Where(w => !Operators.Contains(w));
foreach (var option in opListOfOptions) {
var value = options.Contains(option).ToString();
op = op.Replace(option, value);
}
//return DTEval(op) == 1;
return CompEval(op);
//return XEval(op);
}
static double DTEval(string expression) {
var dt = new DataTable();
var loDataColumn = new DataColumn("Eval", typeof(double), expression);
dt.Columns.Add(loDataColumn);
dt.Rows.Add(0);
return (double)(dt.Rows[0]["Eval"]);
}
static DataTable cDT = new DataTable();
static bool CompEval(string expression) {
return (bool)cDT.Compute(expression, "");
}
public static bool XEval(string expression) {
expression = new System.Text.RegularExpressions.Regex(#"not +(true|false)").Replace(expression.ToLower(), " not(${1}) ");
expression = new System.Text.RegularExpressions.Regex(#"(true|false)").Replace(expression, " ${1}() ");
return (bool)new System.Xml.XPath.XPathDocument(new System.IO.StringReader("<r/>")).CreateNavigator()
.Evaluate(String.Format("boolean({0})", expression));
}
}
The Evaluate method comments show the different options you could use.
Alternatively, you could write your own expression evaluator. A simple one is a recursive descent evaluator that parses a simple grammar. This one uses C# precedence rules, having OR/XOR/AND binding left to right.
public class Evaluator {
// recursive descent boolean expression evaluator
// grammer:
// primary = id
// primary = ( expr )
// unop = primary
// unop = not unop
// andop = unop [ and unop ]*
// xorop = andop [ xor andop ]*
// orop = xorop [ or xorop ]*
// expr = orop
public class TokenList {
List<string> tokens;
int curTokenNum;
static Regex tokenRE = new Regex(#"\w+|[()]", RegexOptions.Compiled);
public TokenList(string expr) {
curTokenNum = 0;
tokens = tokenRE.Matches(expr).Select(m => m.Value).ToList();
}
public string CurToken => curTokenNum < tokens.Count ? tokens[curTokenNum] : String.Empty;
public void MoveNext() => ++curTokenNum;
public bool MoreTokens => curTokenNum < tokens.Count;
public void NextToken() {
MoveNext();
if (!MoreTokens)
throw new InvalidExpressionException("Expected token");
}
}
static List<string> OperatorStrings = new[] { "AND", "OR", "XOR", "NOT" }.ToList();
enum Operators { and, or, xor, not };
static List<string> ParenStrings = new[] { "(", ")" }.ToList();
enum Parens { open, close };
TokenList tokens;
List<string> trueOptions;
public Evaluator(List<string> trueOptions) {
this.trueOptions = trueOptions;
}
string curToken => tokens.CurToken;
bool curTokenValue => trueOptions.Contains(curToken);
bool isOperator => OperatorStrings.FindIndex(s => s.Equals(curToken, StringComparison.OrdinalIgnoreCase)) != -1;
Operators curOp => (Operators)OperatorStrings.FindIndex(s => s.Equals(curToken, StringComparison.OrdinalIgnoreCase));
bool isParen => ParenStrings.Contains(curToken);
Parens curParen => (Parens)(ParenStrings.IndexOf(curToken));
public bool id() {
if (isOperator)
throw new InvalidExpressionException("missing operand");
else {
var ans = curTokenValue;
tokens.MoveNext();
return ans;
}
}
bool primary() {
if (isParen)
if (curParen == Parens.open) {
tokens.NextToken();
var ans = expr();
if (!isParen || curParen != Parens.close)
throw new InvalidExpressionException($"missing ) at {curToken}");
else
tokens.MoveNext();
return ans;
}
else
throw new InvalidExpressionException("Invalid )");
else
return id();
}
bool unop() {
if (isOperator && curOp == Operators.not) {
tokens.NextToken();
return !unop();
}
else
return primary();
}
bool andop() {
var ans = unop();
while (tokens.MoreTokens && isOperator && curOp == Operators.and) {
tokens.NextToken();
ans = ans & unop();
}
return ans;
}
bool xorop() {
var ans = andop();
while (tokens.MoreTokens && isOperator && curOp == Operators.xor) {
tokens.NextToken();
ans = ans ^ andop();
}
return ans;
}
bool orop() {
var ans = xorop();
while (tokens.MoreTokens && isOperator && curOp == Operators.or) {
tokens.NextToken();
ans = ans | xorop();
}
return ans;
}
bool expr() => orop();
public bool Value(string exp) {
tokens = new TokenList(exp);
var ans = expr();
if (tokens.MoreTokens)
throw new InvalidExpressionException($"Unrecognized token {curToken} after expression");
return ans;
}
}
You can call it by creating an Evaluator and passing it an expression to evaluate:
var eval = new Evaluator(CustomerSelectedOptions);
var ans = eval.Value(LogicOperation);
I have some fun with Expressions and a question appears: it throws an exception that I didn't suppose.
I have an input - simple math formula, for example 2*x+3, and I want to create an expression tree for it. So I write this code
using System;
using System.Linq.Expressions;
namespace ConsoleApplication50
{
class Program
{
static void Main()
{
string s = "3*x^2+2/5*x+4";
Expression<Func<double, double>> expr = MathExpressionGenerator.GetExpression(s);
Console.WriteLine(expr);
var del = expr.Compile();
Console.WriteLine(del(10));
}
}
static class MathExpressionGenerator
{
public const string SupportedOps = "+-*/^";
private static readonly ParameterExpression Parameter = Expression.Parameter(typeof(double), "x");
public static Expression<Func<double, double>> GetExpression(string s)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(double), "x");
Expression result = GetExpressionInternal(s);
return Expression.Lambda<Func<double, double>>(result, parameterExpression);
}
private static Expression GetExpressionInternal(string s)
{
double constant;
if (s == "x")
return Parameter;
if (double.TryParse(s, out constant))
return Expression.Constant(constant, typeof(double));
foreach (char op in SupportedOps)
{
var split = s.Split(new[] { op }, StringSplitOptions.RemoveEmptyEntries);
if (split.Length > 1)
{
var expression = GetExpressionInternal(split[0]);
for (int i = 1; i < split.Length; i++)
{
expression = RunOp(expression, GetExpressionInternal(split[i]), op);
}
return expression;
}
}
throw new NotImplementedException("never throws");
}
private static Expression RunOp(Expression a, Expression b, char op)
{
switch (op)
{
case '+':
return Expression.Add(a, b);
case '-':
return Expression.Subtract(a, b);
case '/':
return Expression.Divide(a, b);
case '*':
return Expression.Multiply(a, b);
case '^':
return Expression.Power(a, b);
}
throw new NotSupportedException();
}
}
}
but I get an error:
Unhandled Exception: System.InvalidOperationException: variable 'x' of
type 'Sys tem.Double' referenced from scope '', but it is not defined
at
System.Linq.Expressions.Compiler.VariableBinder.Reference(ParameterExpress
ion node, VariableStorageKind storage) at
System.Linq.Expressions.Compiler.VariableBinder.VisitParameter(ParameterEx
pression node) at
System.Linq.Expressions.ParameterExpression.Accept(ExpressionVisitor
visit or) at ... and so on
Please, advice, how can it be fixed? Here I have a single global parameter and referencing it so I have no idea, why it says this stuff.
Problem with your code is that you have two instances of x parameter. One of them is private static field that is used across expression generation, and second one is that you create and use in Lambda creation.
If you have ParameterExpression, then you should use the same instance in expression and pass the same instance into Lambda generation, otherwise it will fail like in your example.
it will work fine if you remove parameterExpression and will use private Parameter field like this:
public static Expression<Func<double, double>> GetExpression(string s)
{
Expression result = GetExpressionInternal(s);
return Expression.Lambda<Func<double, double>>(result, Parameter);
}
Working example in .NetFiddle - https://dotnetfiddle.net/Onw0Hy
static void Main(string[] args)
{
var str = #"3*x^2+2/5*x+4";
str = Transform(str);
var param = Expression.Parameter(typeof (double), "x");
var expression = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] {param}, null, str);
var exp10 = expression.Compile().DynamicInvoke(10);
Console.WriteLine(exp10);
}
public const string SupportedOps = "+-*/";//separators without ^
private static string Transform(string expression)
{
//replace x^y with Math.Pow(x,y)
var toBeReplaced = expression.Split(SupportedOps.ToCharArray()).Where(s => s.Contains("^"));
var result = expression;
return toBeReplaced.Aggregate(expression, (current, str) => current.Replace(str, string.Format("Math.Pow({0})", str.Replace('^', ','))));
//OR
//foreach (var str in toBeReplaced)
//{
// result =result.Replace(str, string.Format("Math.Pow({0})", str.Replace('^', ',')));
//}
//return result;
}
Given a string containing a mathematical expression, given a set of functions/commands and given a set of assigned variables, are there tools that .NET provides to quickly build a parser?
I would like to build a simple parser that analyzes an expression and breaks it into its simplest components, for example:
d*(abs(a-b)+sqrt(c))
becomes
f = abs(a-b) and g = sqrt(c)
e = f + g
d*e
Do you want to build a parser or just have the solution presented?
Either way, check out nCalc. If you just need to solve it, grab the binaries. If you need to see how they parse out the expression tree, grab the source.
I've heard good things about the Grammatica parser generator. ANTLR is also widely used (especially in Java).
I assume you know how to define a BNF grammar and have learned about or built parsers in the past.
Check out veparser as well. Here is a sample code that shows how you can build an expression evaluator( the code parses the expression and directly calculates the output ). This sample can be modified to store the evaluation tree instead of running it.
using System;
using VeParser;
public class MathEvaluator : CharParser
{
protected override Parser GetRootParser()
{
Func<double, double, double> productFunc = (value1, value2) => value1 * value2;
Func<double, double, double> divideFunc = (value1, value2) => value1 / value2;
Func<double, double, double> sumFunc = (value1, value2) => value1 + value2;
Func<double, double, double> subtractFunc = (value1, value2) => value1 - value2;
Func<double, double> negativeFunc = value => -value;
Func<double, double> posititveFunc = value => value;
var dot = token('.');
var op = token('(');
var cp = token(')');
var sumOp = create(sumFunc, token('+'));
var subtractOp = create(subtractFunc, token('-'));
var positiveOp = create(posititveFunc, token('+'));
var negativeOp = create(negativeFunc, token('-'));
var productOp = create(productFunc, token('*'));
var divideOp = create(divideFunc, token('/'));
// Numbers
var deciamlPlaceValue = 1M;
var decimalDot = run(() => { deciamlPlaceValue = 1; }, dot);
var digit = consume((n, d) => n * 10 + char.GetNumericValue(d), keep(Digit));
var decimalDigit = consume((n, d) => { deciamlPlaceValue = deciamlPlaceValue * 10; return (double)((decimal)n + ((decimal)char.GetNumericValue(d)) / deciamlPlaceValue); }, keep(Digit));
var number = any(
/* float */ create(0, seq(zeroOrMore(digit), decimalDot, oneOrMore(decimalDigit))),
/* int */ create(0, oneOrMore(digit))
);
var expression = createReference();
var simpleExpression = createReference();
// Unary
var unaryOp = any(positiveOp, negativeOp);
var unaryExpression = update(d => d.action(d.value),
createNew(seq(set("action", unaryOp), set("value", expression))));
// Binary
var binaryOp = any(sumOp, subtractOp, productOp, divideOp);
var binaryExpressinoTree = update(x => x.value1, createNew(
seq(
set("value1", simpleExpression),
zeroOrMore(
update(d => { var r = base.CreateDynamicObject(); r.value1 = d.action(d.value1, d.value2); return r; },
seq(
set("action", binaryOp),
set("value2", simpleExpression))))
)));
var privilegedExpressoin = seq(op, expression, cp);
setReference(simpleExpression, any(privilegedExpressoin, unaryExpression, number));
setReference(expression, any(binaryExpressinoTree, simpleExpression));
return seq(expression, endOfFile());
}
public static object Eval(string expression)
{
MathEvaluator me = new MathEvaluator();
var result = me.Parse(expression.ToCharArray());
return result;
}
}
Another Aproach
class Program
{
static void Main(string[] args)
{
var a = 1;
var b = 2;
Console.WriteLine(FN_ParseSnippet($"{a} + {b} * 2"));
Console.ReadKey();
}
public static object FN_ParseSnippet(string snippet)
{
object ret = null;
var usingList = new List<string>();
usingList.Add("System");
usingList.Add("System.Collections.Generic");
usingList.Add("System.Text");
usingList.Add("Microsoft.CSharp");
//Create method
CodeMemberMethod pMethod = new CodeMemberMethod();
pMethod.Name = "Execute";
pMethod.Attributes = MemberAttributes.Public;
pMethod.ReturnType = new CodeTypeReference(typeof(object));
pMethod.Statements.Add(new CodeSnippetExpression(" return " + snippet));
//Create Class
CodeTypeDeclaration pClass = new System.CodeDom.CodeTypeDeclaration("Compilator");
pClass.Attributes = MemberAttributes.Public;
pClass.Members.Add(pMethod);
//Create Namespace
CodeNamespace pNamespace = new CodeNamespace("MyNamespace");
pNamespace.Types.Add(pClass);
foreach (string sUsing in usingList)
pNamespace.Imports.Add(new CodeNamespaceImport(sUsing));
//Create compile unit
CodeCompileUnit pUnit = new CodeCompileUnit();
pUnit.Namespaces.Add(pNamespace);
CompilerParameters param = new CompilerParameters();
param.GenerateInMemory = true;
List<AssemblyName> pReferencedAssemblys = new List<AssemblyName>();
pReferencedAssemblys = Assembly.GetExecutingAssembly().GetReferencedAssemblies().ToList();
pReferencedAssemblys.Add(Assembly.GetExecutingAssembly().GetName());
pReferencedAssemblys.Add(Assembly.GetCallingAssembly().GetName());
foreach (AssemblyName asmName in pReferencedAssemblys)
{
Assembly asm = Assembly.Load(asmName);
param.ReferencedAssemblies.Add(asm.Location);
}
//Compile
CompilerResults pResults = (new CSharpCodeProvider()).CreateCompiler().CompileAssemblyFromDom(param, pUnit);
if (pResults.Errors != null && pResults.Errors.Count > 0)
{
//foreach (CompilerError pError in pResults.Errors)
// MessageBox.Show(pError.ToString());
}
var instance = pResults.CompiledAssembly.CreateInstance("MyNamespace.Compilator");
ret = instance.GetType().InvokeMember("Execute", BindingFlags.InvokeMethod, null, instance, null);
return ret;
}
}
Basically I've wrote my own parser and I'm parsing a string into and expression and then compiling and storing to be reused later on.
For (an odd) example the type of string I'm parsing is this:-
if #name == 'max' and #legs > 5 and #ears > 5 then shoot()
The hash parts in the string are telling my parser to look at the properties on the object of type T that I pass in, if not found to look in a Dictionary that also gets passed in as extra.
I parse up the string into parts create an expression and join the expressions using methods from the PredicateBuilder.
I get the left value from where ever it may be and then the right value and turn it into an int32 or a string based on if it's wrapped in single quotes.. for now, then pass both into Expression.Equals/Expression.GreaterThan etc. dependant on the operator.
So far this works fine for equals and strings but take for example #ears which isn't a property on the object but is in the dictionary I end up with "does string equal int32" and it throws an exception.
I need to be able to parse the string from the dictionary into and int32 and then try the equal/greater/less than method.
Hopefully someone could shed some light on this as I haven't found anything yet that will do it and its driving me mad.
Here is the CreateExpression method I'm using to create an expression from the string.
private Expression<Func<T, IDictionary<string, string>, bool>> CreateExpression<T>(string condition)
{
string[] parts = condition.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 3)
{
var typeCache = _cache[typeof(T).FullName];
var param = Expression.Parameter(typeCache.T, "o");
var param2 = Expression.Parameter(typeof(IDictionary<string, string>), "d");
/* Convert right hand side into correct value type
*/
Expression right = null;
if (parts[2][0] == '\'' && parts[2][parts[2].Length - 1] == '\'')
right = Expression.Constant(parts[2].Trim(new char[] { '\'' }), typeof(string));
else
{
int x = 0;
right = (int.TryParse(parts[2].Trim(), out x))
? Expression.Constant(x)
: Expression.Constant(parts[2].Trim(new char[] { '\'' }), typeof(string));
}
/* Get the left hand side value from object T or IDictionary and then attempt to convert it
* into the right hand side value type
*/
Expression left = null;
var key = (parts[0][0] == '#') ? parts[0].TrimStart('#') : parts[0];
if (_cache[typeCache.T.FullName].Properties.Find(key, true) == null)
{
var m = typeof(ExpressionExtensions).GetMethod("GetValue");
left = Expression.Call(null, m, new Expression[] { param2, Expression.Constant(key) });
}
else
left = Expression.PropertyOrField(param, key);
/* Find the operator and return the correct expression
*/
if (parts[1] == "==")
{
return Expression.Lambda<Func<T, IDictionary<string, string>, bool>>(
Expression.Equal(left, right), new ParameterExpression[] { param, param2 });
}
else if (parts[1] == ">")
{
return Expression.Lambda<Func<T, IDictionary<string, string>, bool>>(
Expression.GreaterThan(left, right), new ParameterExpression[] { param, param2 });
}
}
return null;
}
And here is the method to parse the whole string into parts and build up the expression.
public Func<T, IDictionary<string, string>, bool> Parse<T>(string rule)
{
var type = typeof(T);
if (!_cache.ContainsKey(type.FullName))
_cache[type.FullName] = new TypeCacheItem()
{
T = type,
Properties = TypeDescriptor.GetProperties(type)
};
Expression<Func<T, IDictionary<string, string>, bool>> exp = null;
var actionIndex = rule.IndexOf("then");
if (rule.IndexOf("if") == 0 && actionIndex > 0)
{
var conditionStatement = rule.Substring(2, actionIndex - 2).Trim();
var actionStatement = rule.Substring(actionIndex + 4).Trim();
var startIndex = 0;
var endIndex = 0;
var conditionalOperator = "";
var lastConditionalOperator = "";
do
{
endIndex = FindConditionalOperator(conditionStatement, out conditionalOperator, startIndex);
var condition = (endIndex == -1) ? conditionStatement.Substring(startIndex) : conditionStatement.Substring(startIndex, endIndex - startIndex);
var x = CreateExpression<T>(condition.Trim());
if (x != null)
{
if (exp != null)
{
switch (lastConditionalOperator)
{
case "or":
exp = exp.Or<T>(x);
break;
case "and":
exp = exp.And<T>(x);
break;
default:
exp = x;
break;
}
}
else
exp = x;
}
lastConditionalOperator = conditionalOperator;
startIndex = endIndex + conditionalOperator.Length;
} while (endIndex > -1);
}
else
throw new ArgumentException("Rule must start with 'if' and contain 'then'.");
return (exp == null) ? null : exp.Compile();
}
My eyes are open to suggestions or advice on this but the idea of parsing that string as is to return true or false has to be,
EDIT:
I've now alter my method that retrieves the value from the dictionary to a generic one as Fahad suggested to this:-
public static T GetValue<T>(this IDictionary<string, string> dictionary, string key)
{
string x = string.Empty;
dictionary.TryGetValue(key, out x);
if (typeof(T) == typeof(int))
{
int i = 0;
if (int.TryParse(x, out i))
return (T)(i as object);
}
return default(T);
//throw new ArgumentException("Failed to convert dictionary value to type.");
}
And this works fine but I would rather return null than a default value for the type i've tried using Nullable ... where T : struct and returning null when not found or can't convert but I don't know how to use the null result in my expression.
I think you should redo your problem analysis and try another way. One way I would propose is to have a generic method that would give you the value from a IDictionary or IList etc., and use this method to wrap around your Expression's. The Expression would only want the "Type" of the object to be satisfied, if that is done correctly, then you could easily do it. If you know the "Type" then you can implement it with generic methods, otherwise, you could still use reflection and do generics.
So all you have to think is how to get the value from the dictionary or any other list through expressions.
Hope that helps.
-Fahad
You could use Expression.Call(null, typeof(int), "Parse", Type.EmptyTypes, yourTextExpression)
Instead of using {0} {1}, etc. I want to use {title} instead. Then fill that data in somehow (below I used a Dictionary). This code is invalid and throws an exception. I wanted to know if i can do something similar to what i want. Using {0 .. N} is not a problem. I was just curious.
Dictionary<string, string> d = new Dictionary<string, string>();
d["a"] = "he";
d["ba"] = "llo";
d["lol"] = "world";
string a = string.Format("{a}{ba}{lol}", d);
No, but this extension method will do it
static string FormatFromDictionary(this string formatString, Dictionary<string, string> valueDict)
{
int i = 0;
StringBuilder newFormatString = new StringBuilder(formatString);
Dictionary<string, int> keyToInt = new Dictionary<string,int>();
foreach (var tuple in valueDict)
{
newFormatString = newFormatString.Replace("{" + tuple.Key + "}", "{" + i.ToString() + "}");
keyToInt.Add(tuple.Key, i);
i++;
}
return String.Format(newFormatString.ToString(), valueDict.OrderBy(x => keyToInt[x.Key]).Select(x => x.Value).ToArray());
}
Check this one, it supports formating:
public static string StringFormat(string format, IDictionary<string, object> values)
{
var matches = Regex.Matches(format, #"\{(.+?)\}");
List<string> words = (from Match matche in matches select matche.Groups[1].Value).ToList();
return words.Aggregate(
format,
(current, key) =>
{
int colonIndex = key.IndexOf(':');
return current.Replace(
"{" + key + "}",
colonIndex > 0
? string.Format("{0:" + key.Substring(colonIndex + 1) + "}", values[key.Substring(0, colonIndex)])
: values[key] == null ? string.Empty : values[key].ToString());
});
}
How to use:
string format = "{foo} is a {bar} is a {baz} is a {qux:#.#} is a really big {fizzle}";
var dictionary = new Dictionary<string, object>
{
{ "foo", 123 },
{ "bar", true },
{ "baz", "this is a test" },
{ "qux", 123.45 },
{ "fizzle", DateTime.Now }
};
StringFormat(format, dictionary)
You can implement your own:
public static string StringFormat(string format, IDictionary<string, string> values)
{
foreach(var p in values)
format = format.Replace("{" + p.Key + "}", p.Value);
return format;
}
Phil Haack discussed several methods of doing this on his blog a while back: http://haacked.com/archive/2009/01/14/named-formats-redux.aspx. I've used the "Hanselformat" version on two projects with no complaints.
It's possible now
With Interpolated Strings of C# 6.0 you can do this:
string name = "John";
string message = $"Hi {name}!";
//"Hi John!"
static public class StringFormat
{
static private char[] separator = new char[] { ':' };
static private Regex findParameters = new Regex(
"\\{(?<param>.*?)\\}",
RegexOptions.Compiled | RegexOptions.Singleline);
static string FormatNamed(
this string format,
Dictionary<string, object> args)
{
return findParameters.Replace(
format,
delegate(Match match)
{
string[] param = match.Groups["param"].Value.Split(separator, 2);
object value;
if (!args.TryGetValue(param[0], out value))
value = match.Value;
if ((param.Length == 2) && (param[1].Length != 0))
return string.Format(
CultureInfo.CurrentCulture,
"{0:" + param[1] + "}",
value);
else
return value.ToString();
});
}
}
A little more involved than the other extension method, but this should also allow non-string values and formatting patterns used on them, so in your original example:
Dictionary<string, object> d = new Dictionary<string, object>();
d["a"] = DateTime.Now;
string a = string.FormatNamed("{a:yyyyMMdd-HHmmss}", d);
Will also work...
Since C# 6 released you are able to use String Interpolation feature
Code that solves your question:
string a = $"{d["a"]}{d["ba"]}{d["lol"]}";
Why a Dictionary? It's unnecessary and overly complicated. A simple 2 dimensional array of name/value pairs would work just as well:
public static string Format(this string formatString, string[,] nameValuePairs)
{
if (nameValuePairs.GetLength(1) != 2)
{
throw new ArgumentException("Name value pairs array must be [N,2]", nameof(nameValuePairs));
}
StringBuilder newFormat = new StringBuilder(formatString);
int count = nameValuePairs.GetLength(0);
object[] values = new object[count];
for (var index = 0; index < count; index++)
{
newFormat = newFormat.Replace(string.Concat("{", nameValuePairs[index,0], "}"), string.Concat("{", index.ToString(), "}"));
values[index] = nameValuePairs[index,1];
}
return string.Format(newFormat.ToString(), values);
}
Call the above with:
string format = "{foo} = {bar} (really, it's {bar})";
string formatted = format.Format(new[,] { { "foo", "Dictionary" }, { "bar", "unnecessary" } });
Results in: "Dictionary = unnecessary (really, it's unnecessary)"
public static string StringFormat(this string format, IDictionary<string, object> values)
{
return Regex.Matches(format, #"\{(?!\{)(.+?)\}")
.Select(m => m.Groups[1].Value)
.Aggregate(format, (current, key) =>
{
string[] splits = key.Split(":");
string replacement = splits.Length > 1
? string.Format($"{{0:{splits[1]}}}", values[splits[0]])
: values[key].ToString();
return Regex.Replace(current, "(.|^)("+ Regex.Escape($"{{{key}}}")+")(.|$)",
m => m.Groups[1].ToString() == "{" && m.Groups[3].ToString() == "}"
? m.Groups[2].ToString()
: m.Groups[1] + replacement + m.Groups[3]
);
});
}
This is similar to another answer, but it considers escaping with {{text}}.
I took #LPCRoy's answer and refactored for generic dictionary types, added parameter validation, it only replaces fields it can find, and attempts to solve the "double curly brace" issue by escaping them first, then replacing them at the end with single braces in the same way that string.Format() does.
public static string FormatFromDictionary<K, T>(this string formatString, IDictionary<K, T> valueDict)
{
if (string.IsNullOrWhiteSpace(formatString)) return formatString;
if (valueDict == null || !valueDict.Any()) return formatString;
bool escapedDoubleCurlyBraces = false;
if (formatString.Contains("{{") || formatString.Contains("}}"))
{
formatString = formatString.Replace("{{", "\\{\\{").Replace("}}", "\\}\\}");
escapedDoubleCurlyBraces = true;
}
int i = 0;
StringBuilder newFormatString = new StringBuilder(formatString);
Dictionary<K, int> keyToInt = new Dictionary<K, int>();
string field;
//StringBuilder.Replace() is faster than string.Replace().
foreach (var kvp in valueDict)
{
field = "{" + kvp.Key.ToString() + "}";
if (formatString.Contains(field))
{
newFormatString = newFormatString.Replace(field, "{" + i.ToString() + "}");
keyToInt.Add(kvp.Key, i);
i++;
}
}
//Any replacements to make?
if (keyToInt.Any())
{
formatString = string.Format(
newFormatString.ToString(),
keyToInt.OrderBy(kvp => kvp.Value).Select(kvp => (object)valueDict[kvp.Key]).ToArray()
);
}
if (escapedDoubleCurlyBraces)
{
//Converts "{{" and "}}" to single "{" and "}" for the final result just like string.format().
formatString = formatString.Replace("\\{\\{", "{").Replace("\\}\\}", "}");
}
return formatString;
}
Here is a nice solution that is very useful when formatting emails: http://www.c-sharpcorner.com/UploadFile/e4ff85/string-replacement-with-named-string-placeholders/
Edited:
public static class StringExtension
{
public static string Format( this string str, params Expression<Func<string,object>>[] args)
{
var parameters = args.ToDictionary( e=>string.Format("{{{0}}}",e.Parameters[0].Name), e=>e.Compile()(e.Parameters[0].Name));
var sb = new StringBuilder(str);
foreach(var kv in parameters)
{
sb.Replace( kv.Key, kv.Value != null ? kv.Value.ToString() : "");
}
return sb.ToString();
}
}
Example usage:
public string PopulateString(string emailBody)
{
User person = _db.GetCurrentUser();
string firstName = person.FirstName; // Peter
string lastName = person.LastName; // Pan
return StringExtension.Format(emailBody.Format(
firstname => firstName,
lastname => lastName
));
}
(your Dictionary + foreach + string.Replace) wrapped in a sub-routine or extension method?
Obviously unoptimized, but...