Search code examples
c#dictionarymethodsfilterinstance-variables

In C#, is it possible to create and apply a Dictionary of instance methods?


I currently have the following method:

        private FilterDefinition<BsonDocument> BuildFilters(GenericQueryParameters parameters)
        {
            FilterDefinitionBuilder<BsonDocument> filterBuilder = Builders<BsonDocument>.Filter;
            FilterDefinition<BsonDocument> filters = filterBuilder.Empty;
            foreach (string filterString in parameters.Filters)
            {
                Match match = Regex.Match(filterString, @"(\w+)\s*([<>=]+)\s*(.*)", RegexOptions.IgnoreCase);
                if (match.Success)
                {
                    string fieldName = match.Groups[1].Value;
                    string condition = match.Groups[2].Value;
                    string targetValue = match.Groups[3].Value;

                    if ("==".Equals(condition))
                    {
                        filters = filters & filterBuilder.Eq(fieldName, targetValue);
                    }
                    else if ("!=".Equals(condition))
                    {
                        filters = filters & filterBuilder.Ne(fieldName, targetValue);
                    }
                    else if ("<=".Equals(condition))
                    {
                        filters = filters & filterBuilder.Lte(fieldName, targetValue);
                    }
                    else if ("<".Equals(condition))
                    {
                        filters = filters & filterBuilder.Lt(fieldName, targetValue);
                    }
                    else if (">=".Equals(condition))
                    {
                        filters = filters & filterBuilder.Gte(fieldName, targetValue);
                    }
                    else if (">".Equals(condition))
                    {
                        filters = filters & filterBuilder.Gt(fieldName, targetValue);
                    }
                    else
                    {
                        throw new MalformedDatabaseFilterException($"The condition {condition} could not be applied.");
                    }
                }
                else
                {
                    throw new MalformedDatabaseFilterException($"The filter {filterString} could not be parsed.");
                }
            }

            return filters;
        }

This works, but it is very long and repetative. I'd like to make it something more like:

        private static readonly Dictionary<string, Action> FilterBySymbols = new Dictionary<string, Action>() {
            {"==", FilterDefinitionBuilder<BsonDocument>.Eq },
            {"!=", FilterDefinitionBuilder<BsonDocument>.Ne },
            {"<=", FilterDefinitionBuilder<BsonDocument>.Lte },
            {"<", FilterDefinitionBuilder<BsonDocument>.Lt },
            {">=", FilterDefinitionBuilder<BsonDocument>.Gte },
            {">", FilterDefinitionBuilder<BsonDocument>.Gt }
        };
        private FilterDefinition<BsonDocument> BuildFilters(GenericQueryParameters parameters)
        {
            FilterDefinitionBuilder<BsonDocument> filterBuilder = Builders<BsonDocument>.Filter;
            FilterDefinition<BsonDocument> filters = filterBuilder.Empty;
            foreach (string filterString in parameters.Filters)
            {
                Match match = Regex.Match(filterString, @"(\w+)\s*([<>=]+)\s*(.*)", RegexOptions.IgnoreCase);
                if (match.Success)
                {
                    string fieldName = match.Groups[1].Value;
                    string condition = match.Groups[2].Value;
                    string targetValue = match.Groups[3].Value;
                    if (FilterBySymbols.TryGetValue(condition, out Action action))
                    {
                        filters = filters & action(fieldName, targetValue);
                    }
                    else
                    {
                        throw new MalformedDatabaseFilterException($"The condition {condition} could not be applied.");
                    }
                }
                else
                {
                    throw new MalformedDatabaseFilterException($"The filter {filterString} could not be parsed.");
                }
            }
            return filters;
        }

... but this won't compile since the actions are all instance methods. Also, once I get the method out of the map, there is an open question of how I might apply it to the instance.

Any ideas whether/how I could make the later version work?


Solution

  • You could have a Dictionary<string, Action<FilterDefinitionBuilder<BsonDocument>, string, string>>. Initialize it as:

    { "==", (builder, fieldName, targetValue) => builder.Eq(fieldName, targetValue) },
    { "!=", (builder, fieldName, targetValue) => builder.Neq(fieldName, targetValue) }
    

    and call it as:

    if (FilterBySymbols.TryGetValue(condition, out Action action))
    {
        action(filterBuilder, fieldName, targetValue);
    }
    

    That said, I'd probably write it using a switch expression: that's going to be both clearer and cheaper:

    filters = filters & condition switch
    {
        "==" => filterBuilder.Eq(fieldName, targetValue),
        "!=" => filterBuilder.Neq(fieldName, targetValue),
        ...
        _ => throw new MalformedDatabaseFilterException($"The condition {condition} could not be applied.");
    }