使用 C# 中的表达式将字符串解析为 int32
基本上我已经编写了自己的解析器,并将字符串解析为 和 表达式,然后编译和存储以供稍后重用。
对于(一个奇怪的)例子,我正在解析的字符串类型是这样的: -
if #name == 'max' and #legs > 5 and #ears > 5 then shoot()
字符串中的哈希部分告诉我的解析器查看我传入的类型 T 的对象的属性,如果没有找到要查找的属性也作为额外传入的字典。
我将字符串解析为多个部分,创建一个表达式,并使用 PredicateBuilder 中的方法连接表达式。
我从任何地方获取左值,然后获取右值,并将其转换为 int32 或字符串(基于是否用单引号括起来)。现在,然后将两者传递给 Expression.Equals/Expression.GreaterThan 等取决于运营商。
到目前为止,这对于 equals 和 strings 来说效果很好,但以 #ears 为例,它不是对象的属性,而是在字典中,我最终得到“string equals int32”,并且它抛出异常。
我需要能够将字典中的字符串解析为 int32,然后尝试等于/大于/小于方法。
希望有人能对此有所启发,因为我还没有找到任何可以做到这一点的东西,这让我发疯。
这是我用来从字符串创建表达式的 CreateExpression 方法。
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;
}
这是将整个字符串解析为多个部分并构建表达式的方法。
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();
}
我愿意接受对此的建议或建议,但是解析该字符串以返回 true 或 false 的想法必须是,
编辑:
我现在已经更改了从正如 Fahad 建议的那样,将字典转换为通用字典:-
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.");
}
这工作正常,但我宁愿返回 null 而不是我尝试使用 Nullable ... where T : struct 并在找不到或可以时返回 null 的类型的默认值不转换,但我不知道如何在表达式中使用空结果。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我认为你应该重新分析问题并尝试其他方法。我建议的一种方法是使用一个通用方法,它可以为您提供来自 IDictionary 或 IList 等的值,并使用此方法来包装您的表达式。表达式只希望满足对象的“类型”,如果正确完成,那么您可以轻松做到这一点。如果您知道“类型”,那么您可以使用泛型方法来实现它,否则,您仍然可以使用反射并执行泛型。
因此,您所需要考虑的就是如何通过表达式从字典或任何其他列表中获取值。
希望有帮助。
——法赫德
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
您可以使用Expression.Call(null, typeof(int), "Parse", Type.EmptyTypes, yourTextExpression)
You could use
Expression.Call(null, typeof(int), "Parse", Type.EmptyTypes, yourTextExpression)