开发者

Dynamic string parsing in C#

I'm implementing "type-independent" method for filling DataRow with values. Intended functionality is quite straight forward - caller passes the collection of string representations of column values and DataRow that needs to be filled:

private void FillDataRow(List<ColumnTypeStringRep> rowInput, DataRow row)

ColumnTypeStringRep structure contains the string representation of value, column name, and - what's of most importance - column data type:

private struct ColumnTypeStringRep
{
    public string columnName; public Type type; public string stringRep;
    public ColumnTypeStrinRep(string columnName, Type type, string stringRep)
    {
        this.columnName = columnName; this.type = type; this.stringRep = stringRep;
    }
}

So what's that "type-independency"? Basically I don't care about the data row schema (which always be a row of some typed开发者_如何学Python data table), as long as passed column names match DataRow's colum names and column data types match those of DataRow I'm fine. This function needs to be as flexible as possible (and as simple as possible - just not simpler). Here it is (almost):

private void FillDataRow(List<ColumnTypeStrinRep> rowInput, DataRow row)
{
Debug.Assert(rowInput.Count == row.Table.Columns.Count);

foreach (ColumnTypeStrinRep columnInput in rowInput)
{
    Debug.Assert(row.Table.Columns.Contains(columnInput.columnName));
    Debug.Assert(row.Table.Columns[columnInput.columnName].DataType == columnInput.type);

    // --> Now I want something more or less like below (of course the syntax is wrong) :

    /*
    row[columnInput.columnName] =  columnInput.type.Parse(columnInput.stringRep);
    */

    // --> definitely not like below (this of course would work) :

    /*
    switch (columnInput.type.ToString())
    {
        case "System.String":
            row[columnInput.columnName] = columnInput.stringRep;
            break;
        case "System.Single":
            row[columnInput.columnName] = System.Single.Parse(columnInput.stringRep);
            break;
        case "System.DateTime":
            row[columnInput.columnName] = System.DateTime.Parse(columnInput.stringRep);
            break;
        //...
        default:
            break;
    }
    */
}

}

Now You probably see my problem - I don't want to use the switch statement. It would be perfect, as in the first commented segment, to somehow use the provided column type to invoke Parse method of particular type that returns the object of that type constructed from string representation. The switch solutions works but it's extremely non flexible - what if in future I'll be filling not the DataRow but some other custom type with "columns" that can be of custom type (of course every such type will need to expose Parse method to build itself from string representation).

Hope you got what I mean - its like "dynamic parsing" kind of functionality. Is there a way to achieve this in .NET?

Example of FillDataRow call could look like this:

List<ColumnTypeStrinRep> rowInput = new List<ColumnTypeStrinRep>();
rowInput.Add(new ColumnTypeStringRep("colName1", Type.GetType("System.Int32"), "0"));
rowInput.Add(new ColumnTypeStringRep("colName2", Type.GetType("System.Double"), "1,11"));
rowInput.Add(new ColumnTypeStringRep("colName3", Type.GetType("System.Decimal"), "2,22"));
rowInput.Add(new ColumnTypeStringRep("colName4", Type.GetType("System.String"), "test"));
rowInput.Add(new ColumnTypeStringRep("colName5", Type.GetType("System.DateTime"), "2010-01-01"));
rowInput.Add(new ColumnTypeStringRep("colName6", Type.GetType("System.Single"), "3,33"));

TestDataSet.TestTableRow newRow = this.testDataSet.TestTable.NewTestTableRow();
FillDataRow(rowInput, newRow);
this.testDataSet.TestTable.AddTestTableRow(newRow);
this.testDataSet.TestTable.AcceptChanges();

Thank You!


The TypeConverterclass is the generic .NET way for converting types. The System.ComponentModel namespace includes implementation for primitive types and WPF ships with some more (but I am not sure in which namespace). Further there is the static Convert class offering primitive type conversions, too. It handles some simple conversions on itself and falls back to IConvertible.


First off, your using a struct to pass a complex data around. That is a really bad idea. Make that a class instead.

That said, it sounds like you need a factory to create instances of a parser interface:

interface IColumnTypeParser
{
    // A stateles Parse method that takes input and returns output
    DataColumn Parse(string input);
}

class ColumnTyeParserFactory
{
    IColumnTypeParser GetParser(Type columnType)
    {
        // Implementation can be anything you want...I would recommend supporting
        // a configuration file that maps types to parsers, and use pooling or caching
        // so you are not constantly recreating instances of your parsers (make sure your
        // parser implementations are stateless to support caching/pooling and reuse)

        // Dummy implementation:
        if (columnType == typeof(string)) return new StringColumnTypeParser();
        if (columnType == typeof(float)) return new FloatColumnTypeParser();
        // ...
    }
}

Your FillDataRow implementation would hten use the factory:

m_columnTypeParserFactory = new ColumnTypeParserFactory();

private void FillDataRow(List<ColumnTypeStrinRep> rowInput, DataRow row)
{
    foreach (ColumnTypeStrinRep columnInput in rowInput)
    {
        var parser = m_columnTypeParserFactory.GetParser(columnInput.type);
        row[columnInput.columnName] parser.Parse(columnInput.stringRep);
    }
}


In short there is nothing like that - no automagical conversion. You have to specify conversion yourselves. That said

static class Converters{
    static Dictionary<Type, Func<string, object>> Converters = new ...

    static Converters{
        Converters.Add(typeof(string), input => input);
        Converters.Add(typeof(int), input => int.Parse(input));
        Converters.Add(typeof(double), double => double.Parse(input));
    }
}

void FillDataRow(IList<string> rowInput, row){
    for(int i = 0; i < rowInput.Length; i++){
       var converter = Converters.Converters[Column[i].DataType];
       row[i] = converter(rowInput[i])
    }
}


How about Convert.ChangeType?
You might consider something fancy with generics btw.

foreach (ColumnTypeStrinRep columnInput in rowInput)
{
    Debug.Assert(row.Table.Columns.Contains(columnInput.columnName));
    Debug.Assert(row.Table.Columns[columnInput.columnName].DataType == columnInput.type);
    ...
    row[columnInput.columnName] = Convert.ChangeType(columnInput.stringRep, columnInput.type);
}

More on Convert.ChangeType:
http://msdn.microsoft.com/en-us/library/dtb69x08.aspx

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜