Общий метод анализа без бокса - PullRequest
13 голосов
/ 24 декабря 2008

Я пытаюсь написать универсальный метод Parse, который преобразует и возвращает строго типизированное значение из NamedValueCollection. Я попробовал два метода, но оба эти метода проходят через бокс и распаковку, чтобы получить значение. Кто-нибудь знает способ избежать бокса? Если бы вы видели это в производстве, вам бы не понравилось, насколько это плохо для производительности?

Usuage:

var id = Request.QueryString.Parse<int>("id");

Попытка № 1:

public static T Parse<T>(this NameValueCollection col, string key)
{
    string value = col[key];

    if (string.IsNullOrEmpty(value))
        return default(T);

    if (typeof(T) == typeof(int))
    {
        //return int.Parse(value); // cannot convert int to T
        //return (T)int.Parse(value); // cannot convert int to T
        return (T)(object)int.Parse(value); // works but boxes
    }
    if (typeof(T) == typeof(long))
    {
        return (T)(object)long.Parse(value); // works but boxes
    }
    ...

    return default(T);
}

Попытка № 2 (с использованием отражения):

public static T Parse<T>(this NameValueCollection col, string key)
{
    string value = col[key];

    if (string.IsNullOrEmpty(value))
        return default(T);

    try
    {
        var parseMethod = typeof(T).GetMethod("Parse", new Type[] { typeof(string) });

        if (parseMethod == null)
            return default(T);

        // still boxing because invoke returns an object
        var parsedVal = parseMethod.Invoke(null, new object[] { value });
        return (T)parsedVal;
    }
    // No Proper Parse Method found
    catch(AmbiguousMatchException) 
    {
    }

    return default(T);
}

Ответы [ 7 ]

34 голосов
/ 24 декабря 2008
public static T Parse<T>(this NameValueCollection col, string key)
{
  return (T)Convert.ChangeType(col[key], typeof(T));
}

Я не совсем уверен в полях ChangeType или нет (я думаю, что чтение документов скажет мне, но сейчас у меня мало времени), но, по крайней мере, это избавляет от всей этой проверки типов. Однако накладные расходы на бокс не очень высоки, поэтому я бы не стал сильно беспокоиться об этом. Если вы беспокоитесь о согласованности типов во время выполнения, я напишу функцию как:

public static T Parse<T>(this NameValueCollection col, string key)
{
  T value;

  try
  {
    value = (T)Convert.ChangeType(col[key], typeof(T));
  }
  catch
  {
    value = default(T);
  }

  return value;
}

Таким образом, функция не будет бомбить, если значение не может быть преобразовано по какой-либо причине. Это, конечно, означает, что вам придется проверять возвращаемое значение (что вам придется делать в любом случае, так как пользователь может редактировать строку запроса).

8 голосов
/ 24 декабря 2008

Я думаю, вы переоцениваете влияние бокса / распаковки. Метод синтаксического анализа будет иметь гораздо большие издержки (разбор строк), уменьшая накладные расходы на бокс. Также все операторы if будут иметь большее влияние. Отражение имеет наибольшее влияние из всех.

Я бы не хотел видеть такой код в производстве, так как есть более чистый способ сделать это. Основная проблема, с которой я столкнулся, заключается в большом количестве операторов if, которые вам понадобятся для охвата всех случаев, и в том, что кто-то может передать ему любой старый тип.

Что я хотел бы сделать, это написать функцию синтаксического анализа для каждого типа, который я хочу проанализировать (т.е. ParseInt ()). Это более понятно и четко определено, что попытается сделать функция. Также с короткими статическими методами компилятор, скорее всего, встроит их, сохраняя вызов функции.

Я думаю, что это плохое применение дженериков, какая-то особая причина для этого?

4 голосов
/ 12 сентября 2012

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

var parserFuncs = new Dictionary<Type, Func<string, object>>() {
    { typeof(int), p => (int) int.Parse(p) },
    { typeof(bool), p => (bool) bool.Parse(p) },
    { typeof(long), p => (long) long.Parse(p) },
    { typeof(short), p => (short) short.Parse(p) },
    { typeof(DateTime), p => (DateTime) DateTime.Parse(p) }
    /* ...same for all the other primitive types */
};

return (T) parserFuncs[typeof(T)](value);
4 голосов
/ 07 октября 2011

Я добавлю немного недокументированного способа:

public static T Convert<T>()
{
    if (typeof(T) == typeof(int))
    {
        int a = 5;
        T value = __refvalue(__makeref(a), T);
        return value;
    }
    else if (typeof(T) == typeof(long))
    {
        long a = 6;
        T value = __refvalue(__makeref(a), T);
        return value;
    }

    throw new NotImplementedException();
}

Существует немного документации о них, но они работают с C # 4.0. Прочитайте, например, здесь Скрытые возможности C #? Помните, что недокументированные средства не поддерживаются, бла-бла-бла не может работать в будущем, бла-бла-бла, если вы их используете, дьявол придет за вами, бла-бла-бла: -)

1 голос
/ 17 марта 2013

Еще одно предложение для реализации, использующее метод TryParse или Parse с общим подходом. Первоначально я написал это для преобразования строк, проанализированных из файла CSV, в различные типы: int, decimal, list и т. Д.

 public static bool TryParse<T>(this string value, out T newValue, T defaultValue = default(T))
        where T : struct, IConvertible
    {
        newValue = defaultValue;
        try
        {
            newValue = (T)Convert.ChangeType(value, typeof(T));
        }
        catch
        {
            return false;
        }
        return true;
    }

    public static T Parse<T>(this string value)
        where T : struct, IConvertible
    {
        return (T) Convert.ChangeType(value, typeof (T));
    }

Здесь метод try parse сначала устанавливает newValue в значение по умолчанию, затем пытается преобразовать значение в тип T и возвращает newValue в качестве типа T. Если преобразование завершается неудачно, возвращается значение по умолчанию T.

Метод Parse просто пытается выполнить преобразование, однако, если он небезопасен, и выдаст исключение в случае сбоя преобразования.

0 голосов
/ 03 октября 2012

Вот предложение для реализации, следуя логике Роберта Вагнера, но используя общий подход для уменьшения дублирования:

public static int ParseInt32(this NameValueCollection col, string key)
{
    return Parse(col, key, int.Parse);
}
public static double ParseDouble(this NameValueCollection col, string key)
{
    return Parse(col, key, double.Parse);
}
private static T Parse<T>(NameValueCollection col, string key, Func<string, T> parse)
{
    string value = col[key];

    if (string.IsNullOrEmpty(value))
        return default(T);

    return parse(value);
}

По правде говоря, возвращение нуля для нулевой или пустой строки пугает меня; это может вызвать проблемы, если некоторые значения законно равны нулю. Вместо этого я хотел бы, чтобы методы возвращали nullables (int?, double? и т. Д.), Что является несколько более компактным подходом, чем шаблон out-параметра, используемый для каркасных методов TryParse. Вы можете сделать это:

public static int? ParseInt32(this NameValueCollection col, string key)
{
    return Parse(col, key, int.Parse);
}
public static double? ParseDouble(this NameValueCollection col, string key)
{
    return Parse(col, key, double.Parse);
}
private static T? Parse<T>(NameValueCollection col, string key, Func<string, T> parse)
    where T : struct    
{
    string value = col[key];

    if (string.IsNullOrEmpty(value))
        return default(T?);

    return parse(value);
}

Но это все равно вызовет исключение для непустых или пустых строк, которые не являются числовыми. Лучше использовать TryParse. Встроенные делегаты Func не поддерживают параметры ref или out, поэтому вам придется объявить свой собственный тип делегата, но это довольно тривиально.

0 голосов
/ 16 ноября 2009
int value = int.Parse(Request.QueryString["RecordID"]);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...