Dictionary of generic delegate Parser

2019-03-04 07:30发布

问题:

I want to create a parser that convert string tokens to typed objects based on a key.

My first stab, borrowing ideas from Dictionary<T,Delegate> with Delegates of different types: Cleaner, non string method names?

delegate T Parser<T>(string item);

public class TableParser    
{
    static IDictionary<string, Pair<PropertyInfo, Delegate>> _PARSERS;
    static Type DOMAIN_TYPE;

    static TableParser()
    {
        DOMAIN_TYPE= typeof(Domain);

        Dictionary<string, Pair<PropertyInfo, Delegate>> parsers = new
            Dictionary<string, Pair<PropertyInfo, Delegate>>()
        {
            { "PropertyOne", new Pair<PropertyInfo,Delegate>(
                DOMAIN_TYPE.GetProperty("PropertyOne"), 
                (Parser<double>) double.Parse ) },
        };
        _PARSERS = parsers;
    }

    public List<Domain> Parse(string filename)
    {
        List<Domain> domains = new List<Domain>();

        List<List<string>> data = 
            CVSParser.Instance.Parse(filename);
        List<string> headers = data[0];

        for (int i = 1; i < data.Count; i++)
        {
            List<string> row = data[i];
        }            

        return domains;
    }

    private Dictionary<int, Pair<PropertyInfo, Delegate>> FindParsers(List<string> headers)
    {
        Dictionary<int, Pair<PropertyInfo, Delegate>> parsers =
            new Dictionary<int, Pair<PropertyInfo, Delegate>>();

        int i = 0;
        headers.ForEach(h =>
        {
            if (_PARSERS.ContainsKey(h))
            {
                parsers[i] = _PARSERS[h];
            }
            ++i;
        });

        return parsers;
    }

    private Domain Create(List<string> data,
        Dictionary<int, Pair<PropertyInfo, Delegate>> parsers)
    {
        Domain domain = new Domain();

        foreach (KeyValuePair<int, Pair<PropertyInfo, Delegate>> parser in parsers)
        {
            string datum = data[parser.Key];
            parser.Value.First.SetValue(domain,
                /* got stuck here */ parser.Value.Second,
                null);

        }

        return domain;
    }
}

I got stuck at re-discovering the type of the parser when I need to use it. I need to cast it back to Parser<double>, Parser<int>, etc depending on PropertyInfo.

A canned CSV parser doesn't work in this case because Domain's properties come from multiple files.

回答1:

Although I may not have the full picture, it looks like you're making things more complicated than they need to be.

A simple switch on the PropertyType and direct invocations of the appropriate parse methods would make the code simpler to understand and maintain. If the cost of reflection worries you, use a library to generate delegates that refer directly to the property.

That said, to make your current solution work, the easiest is probably to use dynamic invocation of the delegates:

parser.Value.First.SetValue(analytic, 
    parser.Value.Second.DynamicInvoke(datum),
    null);

I also cannot resist mentioning the extension methods provided by the library referenced above, which allows you to create object instances from a set of values (the data types do not need to match, types are automatically converted/coerced as needed), like this:

var properties = new [] { "One", "Two" };
var inputValues = new object[] { 1.0d, "foobar" };
var domain = typeof(Domain).TryCreateInstance( properties, inputValues );


回答2:

If its not absolutely necessary to create your own parser, use Irony. Its in C# and easy to use. Why re-invent?!



回答3:

If you want to new up object based on some key, please have a look at this question, it seems quite similar. In your case it sounds like you wouldn't even have to use reflection because the types of objects you want to create are already known.

Can they all share an Interface? in that case you can have an abstract MyInterface CreateOne() method that news up an instance that you can implement on each class. Else the approach from the question would probably work best.

Regards GJ