无拳击的通用解析方法

时间:2008-12-23 22:47:04

标签: c# generics reflection

我正在尝试编写一个通用的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);
}

8 个答案:

答案 0 :(得分:34)

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;
}

这样,如果由于某种原因无法转换值,函数将不会弹出。这意味着,当然,您必须检查返回的值(无论如何您都必须这样做,因为用户可以编辑查询字符串)。

答案 1 :(得分:8)

我认为你过度估计拳击/拆箱的影响。解析方法将有更大的开销(字符串解析),使拳击开销相形见绌。此外,所有if语句都会产生更大的影响。反思影响最大。

我不希望在生产中看到这种代码,因为有一种更简洁的方法。我遇到的主要问题是你需要覆盖所有案件的大量if语句以及有人可以将任何旧类型传递给它的事实。

我要做的是为每个要解析的类型(即ParseInt())编写一个解析函数。它更清晰,并且很好地定义了该函数将尝试做什么。此外,对于短静态方法,编译器更可能内联它们,从而保存函数调用。

我认为这是仿制药的一个不好的应用,这样做的任何特殊原因?

答案 2 :(得分:4)

我将添加一些未记录的方式:

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相同。请在此处阅读Hidden Features of C#?请记住,无证件意味着不受支持,等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等等。

答案 3 :(得分:4)

为了更好的可读性,您可以使用具有匿名函数的通用字典,如下所示:

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 :(得分:1)

使用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设置为默认值,然后尝试将value转换为类型T并将newValue作为类型T返回。如果转换失败,则返回默认值T.

Parse方法只是尝试进行转换,但是如果它没有安全失败,并且如果转换失败则抛出异常。

答案 5 :(得分:0)

int value = int.Parse(Request.QueryString["RecordID"]);

答案 6 :(得分:0)

根据Robert Wagner的逻辑,这是一个实施建议,但使用通用方法来减少重复:

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?等),这是一种比用于框架TryParse方法的out-parameter模式稍微紧凑的方法。你可以这样做:

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参数,因此你必须声明自己的委托类型,但这非常简单。

答案 7 :(得分:0)

我来晚了吗?

static Dictionary<Type, Delegate> table = 
    new Dictionary<Type, Delegate>{
        { typeof(int), (Func<string,int>)Int32.Parse },
        { typeof(double), (Func<string,double>)Double.Parse },
        // ... as many as you want
    };

static T Parse<T>(string str)
{
    if (!table.TryGet(typeof(T), out Delegate func))
        throw new ArgumentException();
    var typedFunc = (Func<string, T>)func;
    return typedFunc(str);
}

遇到类型问题时,请尝试使用委托和字典!