C# 没有装箱的通用解析方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/390286/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 01:39:51  来源:igfitidea点击:

Generic Parse Method without Boxing

c#genericsreflection

提问by bendewey

I am trying to write a generic Parse method that converts and returns a strongly typed value from a NamedValueCollection. I tried two methods but both of these methods are going through boxing and unboxing to get the value. Does anyone know a way to avoid the boxing? If you saw this in production would you not like it, how bad is it for performance?

我正在尝试编写一个通用的 Parse 方法,该方法从 NamedValueCollection 转换并返回一个强类型值。我尝试了两种方法,但这两种方法都通过装箱和拆箱来获取值。有谁知道避免拳击的方法吗?如果你在生产中看到这个,你会不会不喜欢它,它的性能有多糟糕?

Usuage:

用法:

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

Attempt #1:

尝试#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);
}

Attempt #2 (using reflection):

尝试#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);
}

采纳答案by Robert Wagner

I think you are over estimating the impact of the boxing/unboxing. The parse method will have a much bigger overhead (string parsing), dwarfing the boxing overhead. Also all the if statements will have a bigger impact. Reflection has the biggest impact of all.

我认为您高估了装箱/拆箱的影响。parse 方法将有更大的开销(字符串解析),使装箱开销相形见绌。此外,所有的 if 语句都会产生更大的影响。反射具有最大的影响。

I'd would not like to see this kind of code in production, as there is a cleaner way of doing it. The major problem I have with it is the large number of if statements you will need to cover all cases and the fact that someone could pass any old type to it.

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

What I would do is write a parse function for each type I want to parse (ie ParseInt()). It's clearer and it is well defined what the function will try to do. Also with short static methods, the compiler is more likely to inline them, saving a function call.

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

I think this is a bad application of generics, any particular reason for doing it this way?

我认为这是泛型的一个糟糕的应用,这样做有什么特别的原因吗?

回答by Robert C. Barth

public static T Parse<T>(this NameValueCollection col, string key)
{
  return (T)Convert.ChangeType(col[key], typeof(T));
}

I'm not entirely sure of ChangeType boxes or not (I guess reading the docs would tell me, but I'm pressed for time right now), but at least it gets rid of all that type-checking. The boxing overhead is not very high, though, so I wouldn't worry too much about it. If you're worried about run-time type consistency, I'd write the function as:

我不完全确定 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;
}

This way the function won't bomb if the value cannot be converted for whatever reason. That means, of course, that you'll have to check the returned value (which you'd have to do anyway since the user can edit the querystring).

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

回答by Boonge

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

回答by xanatos

I'll add a little undocumented way:

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

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

There is little documentation about them, but they work as of C# 4.0. Read for example here Hidden Features of C#?Remember that undocumented means unsupported, blah blah blah could not work in the future blah blah blah if you use them the devil will come for you blah blah blah :-)

关于它们的文档很少,但它们从 C# 4.0 开始工作。例如在这里阅读C# 的隐藏功能?请记住,无证意味着不受支持,等等等等将来无法工作等等等等,如果你使用它们,魔鬼会来找你等等:-)

回答by DavidWainwright

For better readability, you could use a generic dictionary with an anonymous function as follows:

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

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

回答by phoog

Here's a suggestion for implementation, following Robert Wagner's logic, but using a generic approach to reduce duplication:

这是一个实现建议,遵循罗伯特瓦格纳的逻辑,但使用通用方法来减少重复:

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

Truth be told, returning zero for a null or empty string scares me; this could cause problems if some values are legitimately zero. Instead, I would have the methods return nullables (int?, double?, etc.), which is a slightly more compact approach than the out-parameter pattern used for the framework TryParsemethods. You could do this:

说实话,为 null 或空字符串返回零让我感到害怕;如果某些值合法为零,这可能会导致问题。相反,我会让方法返回可空值(int?double?等),这比用于框架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);
}

But that would still throw an exception for non-null-or-empty strings that aren't numeric. It's better to use TryParse. The built-in Func delegates don't support ref or out parameters, so you'd have to declare your own delegate type, but that is fairly trivial.

但是对于非数字的非空或空字符串,这仍然会引发异常。最好使用 TryParse。内置的 Func 委托不支持 ref 或 out 参数,因此您必须声明自己的委托类型,但这很简单。

回答by Chutes

Another suggestion for implementation, using a TryParse or Parse method with a generic approach. I wrote this originally to convert strings parsed from a csv file into different types, int, decimal, list, etc.

另一个实现建议,使用具有通用方法的 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));
    }

Here, the try parse method first sets the newValue to the default value, then tries to convert value to type T and return the newValue as type T. If the conversion fails, it returns the default value of T.

这里,try parse 方法首先将newValue 设置为默认值,然后尝试将value 转换为T 类型并将newValue 作为T 类型返回。如果转换失败,则返回T 的默认值。

The Parse method, simply tries to do the conversion, however if its not fail safe and will throw an exception if the conversion fails.

Parse 方法只是尝试进行转换,但是如果它不是失败安全的并且在转换失败时会抛出异常。

回答by kevinjwz

Am I too late?

我来晚了吗?

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

When in trouble with types, try delegates and dicts!

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