Разобрать строку в int32, используя выражения в C # - PullRequest
0 голосов
/ 20 октября 2010

По сути, я написал свой собственный анализатор, и я анализирую строку и выражение, а затем компилирую и сохраняю для повторного использования позже.

Для (странного) примера типа строки I 'Разбор m таков: -

if #name == 'max' and #legs > 5 and #ears > 5 then shoot()

Хеш-части в строке говорят моему синтаксическому анализатору взглянуть на свойства объекта типа T, который я передаю, если не найдено, чтобы искать в словаре, которыйтакже передается как extra.

Я разбираю строку на части, создаю выражение и соединяю выражения, используя методы из PredicateBuilder.

Я получаю левое значение, откуда бы оно ни былоа затем правильное значение и превратить его в int32 или строку на основе, если оно заключено в одинарные кавычки ... на данный момент, а затем передать оба в Expression.Equals / Expression.GreaterThan и т. д. в зависимости от оператора.

До сих пор это работает нормально для equals и strings, но возьмем, например, #ears, который не является свойством объекта, но находится в словаре, с которым я в конечном итоге "делает строку equal 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, должно быть

РЕДАКТИРОВАТЬ:

Теперь я изменил свой метод, который извлекает значение из словаря в общийФахад предложил следующее: -

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 ... где T: struct и возвращать NULL, когда нетнашел оr не может преобразовать, но я не знаю, как использовать нулевой результат в моем выражении.

Ответы [ 2 ]

1 голос
/ 12 февраля 2011

Вы можете использовать Expression.Call(null, typeof(int), "Parse", Type.EmptyTypes, yourTextExpression)

1 голос
/ 20 октября 2010

Я думаю, вам следует повторить анализ проблемы и попробовать другой способ. Один из способов, который я бы предложил, - это использовать универсальный метод, который даст вам значение из IDictionary, IList и т. Д., И использовать этот метод для обертывания ваших выражений. Выражение только желает, чтобы «Тип» объекта был удовлетворен, если это сделано правильно, то вы можете легко это сделать. Если вы знаете «Тип», то можете реализовать его с помощью универсальных методов, в противном случае вы все равно можете использовать рефлексию и создавать обобщенные методы.

Итак, все, что вам нужно подумать, - это как получить значение из словаря или любого другого списка с помощью выражений.

Надеюсь, это поможет.

-Fahad

...