3

I want to convert a string to a given generic type T. It may be either basic type or string (e.g. int or string), or an array of a basic types or strings (e.g. int[] or string[]). I have the following function:

T Str2Val<T>(string str)
{
  return (T)Convert.ChangeType(str, typeof(T));
}

It works well for basic types. But it fails for T being an array E[] and str being a comma-separated list of values.

I can easily check whether T is an array with typeof(T).IsArray. Then I have two solutions: parse an array in the same function with a scalar, like the following:

  if (!typeof(T).IsArray)
  {
    return (T)Convert.ChangeType(str, typeof(T));
  }
  else
  {
    // Handle an array
  }

or implement two overloaded functions: one for generic T and second for generic E[]. However, both of solutions fail. I cannot use any array-specific code in the else-clause since it must be compatible with a scalar. And C# cannot pick proper overload with E[] when T is actually an array.

What should I do?

8
  • 1
    How are you going to convert string to array and to array of what? char[]? use ToCharArray. int[]? How are you going to split it: comma, dash, dot? Commented Jan 10, 2013 at 11:50
  • I don't want to convert string to array. String should be converted to string (i.e. no changes). Array of strings is of now interest for my task, so whatever splitting is acceptable. Arrays of numbers should be splat with commas. Commented Jan 10, 2013 at 11:53
  • So are you trying to convert int[] to string? Commented Jan 10, 2013 at 11:59
  • No, for T == int[] I'm trying to convert string to int[], like "1,2,3" to {1, 2, 3}. Commented Jan 10, 2013 at 12:01
  • 1
    You should check if T implements IConvertible. Otherwise, there's no hope it will succeed with Convert.ChangeType. You could do like this: if (typeof(IConvertible).IsAssignableFrom(typeof(T))) { return (T)Convert.ChangeType(str, typeof(T)); } /* otherwise, check if T is a collection, and so on. */. Commented Jan 10, 2013 at 14:10

3 Answers 3

7

I would create some listing of custom parsers that you register then later leverage since you seem to be wanting to use custom rules anyway:

public static class StringParsers
{
    private static Dictionary<Type, object> Parsers = new Dictionary<Type, object>();

    public static void RegisterParser<T>(Func<string, T> parseFunction)
    {
        Parsers[typeof(T)] = parseFunction;
    }

    public static T Parse<T>(string input)
    {
        object untypedParser;
        if (!Parsers.TryGetValue(typeof(T), out untypedParser))
            throw new Exception("Could not find a parser for type " + typeof(T).FullName);

        Func<string, T> parser = (Func<string, T>)untypedParser;

        return parser(input);
    }
}

During your application initialization, you would register the types you intend to use later in your application (I'm guessing this is known since you're using generics):

StringParsers.RegisterParser<string[]>(input => input.Split(','));
StringParsers.RegisterParser<int[]>(input => input.Split(',').Select(i => Int32.Parse(i)).ToArray());
StringParsers.RegisterParser<int>(input => Int32.Parse(input));

Finally, you can call it simply:

string testArrayInput = "1,2,8";

int[] integers = StringParsers.Parse<int[]>(testArrayInput); // {1, 2, 8}

string[] strings = StringParsers.Parse<string[]>(testArrayInput); // {"1", "2", "8"}

int singleInt = StringParsers.Parse<int>("9999"); //9999

Now, this is a pretty simple implementation. You may wish to extend it so instead of using type Func<string, T> it might use an IStringParser interface and you can provide deeper implementations of the parsing if necessary. Furthermore, you may wish to make it thread safe (unless you're sure that won't be an issue, or if you are sure your registration on startup is before any usages)

EDIT: If you really, really, really want it all in one function just accounting for your comma delimited array, then you can use this:

public static T Str2Val<T>(string str)
{
    if (!typeof(T).IsArray)
        return (T)Convert.ChangeType(str, typeof(T));

    Type elementType = typeof(T).GetElementType();

    string[] entries = str.Split(',');
    int numberOfEntries = entries.Length;

    System.Array array = Array.CreateInstance(elementType, numberOfEntries);

    for(int i = 0; i < numberOfEntries; i++)
        array.SetValue(Convert.ChangeType(entries[i], elementType), i);

    return (T)(object)array;
}

But this feels so wrong. There must be a better way and to avoid the double generic input in Alexander's answer, but there you go.

Sign up to request clarification or add additional context in comments.

9 Comments

Are you sure the conversion (Func<string, T>)untypedParser; is possible for T being an array?
@Mikhail Yes, The code above is tested and working. T when called is already type (for example) int[].
Ok, I think it is a working solution. Though it's really high price for just being able to handle arrays, compared with my initial one-line function. Anyway, thanks for it.
@Mikhail I added an edit with an all-in-one function, but I don't like it. :) (also tested and working)
My god, it works! I have no idea why (T)(object) works while just (T) doesn't, but it's what I need! And it's the only thing I thought is impossible. Thanks a lot!
|
3

Another solution with explicit element type passing:

static T Str2Val<T>(string str)
{
    return (T)Convert.ChangeType(str, typeof(T));
}

static E[] Str2Val<T, E>(string str)
    where T : ICollection<E>
{
    return str.Split(',').Select(x => (E)Convert.ChangeType(x, typeof(E))).ToArray();
}

using

Str2Val<int>("1")
Str2Val<int[], int>("1,3")

1 Comment

Nice, but it requires changes in the interface of Str2Val and code duplication.
0

Maybe something like this will help:

dynamic Str2Val<T>(string str)
{
    if (!typeof(T).IsArray)
        return (T)Convert.ChangeType(str, typeof(T));

    var elementType = typeof(T).GetElementType();
    return str.Split(',').Select(x => Convert.ChangeType(x, elementType)).ToArray();
}

1 Comment

This will convert to object[], not to E[].

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.