Переменная, разделенная запятыми, в общий список

Мне удалось преобразовать строку, разделенную запятыми, в IList<int>, но как я могу изменить ее, чтобы получить IList<T>, где T будет передан как один из входных параметров?

i.e, если мне нужно IList<int>, я передам "int" в качестве параметра, если мне нужно IList<string>, я передам "string" в качестве параметра.

Моя идея - получить тип, будь то int или string через входной параметр, и использовать отражение и преобразовать строку в соответствующий список

Код для преобразования строки, разделенной запятой, как IList<int>

public static IList<int> SplitStringUsing(this string source, string seperator =",")
{
     return source.Split(Convert.ToChar(seperator))
                  .Select(x => x.Trim())
                  .Where(x => !string.IsNullOrWhiteSpace(x))
                  .Select(int.Parse).ToList();
}

Примечание: Выше код еще не протестирован

Я ищу что-то вроде

public static IList<T> SplitStringUsing(this string source, string seperator =",", T t)
{
find the type of t and convert it to respective List
}

Ответ 1

Вы можете использовать Convert.ChangeType(объект, строка) для синтаксического анализа базовых типов, поддерживаемых System.Convert или любой другой класс, который реализует IConvertible interface

public static IList<T> SplitStringUsing<T>(string source, string seperator = ",")
where T:IConvertible
{
        return source.Split(Convert.ToChar(seperator))
                     .Where(x => !string.IsNullOrWhiteSpace(x))
                     .Select(x=>Convert.ChangeType(x,typeof(T)))
                     .Cast<T>()
                     .ToList();
}

Чтобы избежать проблем с локализацией, вы должны, вероятно, добавить параметр IFormatProvider, чтобы позволить вызывающему указать культуру для использования или по умолчанию для текущей культуры, например:

public static IList<T> SplitStringUsing<T>(string source, 
    string seperator = ",",
    IFormatProvider provider =null)
    where T:IConvertible
{
    return source.Split(Convert.ToChar(seperator))
                    .Where(x => !string.IsNullOrWhiteSpace(x))
                    .Select(x=>Convert.ChangeType(x,typeof(T),provider))
                    .Cast<T>().ToList();
}

Для более общего случая вы можете передать код разбора в виде лямбда для функции:

    public static IList<T> SplitStringUsing<T>(string source, 
        Func<string,T> parser,  
        string seperator = ",")
    {
        return source.Split(Convert.ToChar(seperator))
            .Where(x => !string.IsNullOrWhiteSpace(x))
            .Select(parser)
            .ToList();
    }

и назовите его следующим образом:

var l1 = SplitStringUsing(x,s=>double.Parse(s,NumberStyles.HexNumber,
                                              CultureInfo.InvariantCulture));

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

Ответ 2

Мне кажется, вам нужно Convert.ChangeType, вот так. Он не полностью протестирован, компилируется и исправляется.

public static IList<T> SplitStringUsing(string source, string seperator =",")
    {
         return source.Split(Convert.ToChar(seperator))
                      .Select(x => x.Trim())
                      .Where(x => !string.IsNullOrWhiteSpace(x))
                      .Select((T)Convert.ChangeType( x, typeof( T ) )).ToList();
    }

Ответ 3

Я хотел бы продлить ответ @PanagiotisKanavos.

В частности, общий подход:

public static class StringToListExtension
{
    //see https://msdn.microsoft.com/en-us/library/System.String.Split.aspx
    //and https://msdn.microsoft.com/en-us/library/bb548891.aspx

    //this is the generic approach offering enough possibilies to use
    public static IEnumerable<TResult> MapStringValues<TResult>(this String source, Func<String, TResult> itemMapper, String[] separator, StringSplitOptions options)
    {
        if (null == source) throw new ArgumentNullException("source");
        return source.Split(separator, options).Select(itemMapper);
    }

    //add your implementation using MapStringValues<T>
    public static IList<Int32> MapToInt32ListUsingParse(this String source, String[] separator, StringSplitOptions options)
    {
        return MapStringValues<Int32>(source, Int32.Parse, separator, options).ToList();
    }

    //or more convenient
    public static IList<Int32> DefaultMapToIntList(this String source)
    {
        return MapStringValues<Int32>(source, Int32.Parse, DefaultSeparator, StringSplitOptions.RemoveEmptyEntries).ToList();
    }

    private static readonly String[] DefaultSeparator = new []{ "," };
}

Вы бы использовали этот код:

String values = "some,text,with,commas";
List<String> l1 = values.MapStringValues<String>(s => s, new []{ "," }, StringSplitOptions.None).ToList();

values = "2,4,,5,6";
IList<Int32> l2 = values.MapToInt32ListUsingParse(new []{ "," }, StringSplitOptions.RemoveEmptyEntries);

values = "2,4,,5,6";
IList<Int32> l3 = values.DefaultMapToIntList();

Вы можете добавить варианты удобства для всех случаев с String to T. Если вы не хотите, чтобы исключения были выбраны, просто выполните функцию синтаксического анализа, используя Int32.TryParse и т.д.