泛型和可空类型

时间:2009-12-10 04:55:35

标签: c# generics nullable

假设我有一个方法,它将int作为字符串,如果解析成功则返回int,否则返回null值。

    int? ParseValue(string intAsString)
    {
        int i;
        if (int.TryParse(intAsString, out i))
            return i;
        return null;
    }

如何重写此方法,使其不仅可以使用int?,还可以使用long ?, decimal?和日期时间? ?

7 个答案:

答案 0 :(得分:14)

你应该提一下这很有趣,因为前几天我正在搞这样的事情:

using System;
using System.Reflection;

static class Example
{
    public static Tuple<Boolean, T?> TryParse<T>(this String candidate)
        where T : struct
    {
        T? value = null;
        Boolean success = false;

        var parser = ParsingBinder<T>.GetParser();

        try 
        { 
                value = parser(candidate);
                success = true;
        } 
        catch (FormatException) { }

        return new Tuple<Boolean,T?>(success, value);
    }
}

static class ParsingBinder<T>
{
    static Func<String, T> parser;

    public static Func<String, T> GetParser()
    {
        if (parser == null)
                parser = getParser();

        return parser;
    }

    static Func<String, T> getParser()
    {
        MethodInfo methodInfo 
            = typeof(T).GetMethod(
                    "Parse", new [] { typeof(String) });

        if (methodInfo == null)
                throw new Exception(
                        "Unable to retrieve a \"Parse\" method for type.");

        return (Func<String, T>)Delegate
        .CreateDelegate(typeof(Func<String, T>), methodInfo);
    }
}

这是一种类似的方法,但将其视为更好的TryParse方法,它返回Tuple<Boolean, T?>(这需要.NET 4)。元组的第一个属性是指示解析尝试成功或失败的布尔值,第二个属性是类型为泛型类型参数的可空值,如果解析失败则为null,如果解析成功,则为值

它的工作原理是使用反射从泛型类型参数中检索静态Parse(String)方法,并为传入的字符串调用该方法。我将其构建为扩展方法,允许您执行此类操作:

var intValue = "1234".TryParse<Int32>();
var doubleValue = "1234".TryParse<Double>();

不幸的是,这不适用于enums,因为它们没有相同的parse方法签名,因此您无法使用此扩展来解析enum,但它不会难以破解这一点,以便为枚举创作一个特例。

这种方法的一个好处是,通过反射检索Parse方法的成本仅在首次使用时产生,因为为所有后续使用创建了静态委托。


还有一件事 - 这种方法唯一令人笨拙的事情是没有语言扩展或语法糖可以使这很容易使用。我希望使用此代码实现的是使用BCL中存在的标准TryParse方法的 less 笨重方式。

我个人认为这种模式很难看:

Int32 value;
if (Int32.TryParse(someString, out value))
    // do something with value

主要是因为它需要提前声明变量并使用out参数。我上面的方法并没有那么好:

var result = someString.TryParse<Int32>();
if (result.Item1)
    // do something with result.Item2

真正很酷的是看到一个C#语言扩展程序,它是为Tuple<Boolean, T?>工作而构建的,它允许我们顺利地使用这种类型,但我感觉我写的更多关于此它似乎并不可行。

答案 1 :(得分:3)

您可以显式使用Nullable关键字,而不是使用问号: 例如,

int?等于Nullable<int>

因此,将原始设计切换为Nullable<T> ParseValue(string valueAsString)应该可以解决这个问题:在此之后执行通用实现。

答案 2 :(得分:3)

最好实现Extension方法,甚至可以解析Enumerations。通过这种方式,您可以获得Nullable&lt; ForAnyValueType&gt;像这样:

public static T? Parse<T>(this string text) where T: struct
    {
        object o = null;
        try { 
            var ttype = typeof(T);
            if (ttype.IsEnum)
            {
                T n = default(T);
                if (Enum.TryParse<T>(text, true, out n))
                    return n;
            }
            else
            o = Convert.ChangeType(text, ttype); 
        }
        catch { }

        if (o == null)
            return new Nullable<T>();

        return new Nullable<T>((T)o);
    }

答案 3 :(得分:2)

如果您可以等待C#4.0,则可以使用dynamic关键字来解决此类问题。

答案 4 :(得分:1)

我真的不明白为什么在安德鲁斯解决方案中使用Tuple,只要我们无论如何返回Nullable,似乎两次做同样的事情。我编辑了他的解决方案以使用TryParse并允许返回Nullable或指定为参数的默认值。

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="aText"></param>
    /// <returns></returns>
    public static Nullable<T> TryParse<T>(this string aText) where T : struct {
        T value;
        if (ParsingBinder<T>.TryParse(aText, out value)) {
            return value;
        }
        return null;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="aText"></param>
    /// <param name="aDefault"></param>
    /// <returns></returns>
    public static T TryParse<T>(this string aText, T aDefault) where T : struct {
        T value;
        if (!ParsingBinder<T>.TryParse(aText, out value)) {
            value = aDefault;
        }
        return value;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    static class ParsingBinder<T> where T : struct {

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="aText"></param>
        /// <param name="aOutput"></param>
        /// <returns></returns>
        public delegate bool Delegate_TryParse<T>(string aText, out T aOutput) where T : struct;

        /// <summary>
        /// 
        /// </summary>
        static Delegate_TryParse<T> methodTryParse;

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public static Delegate_TryParse<T> TryParse {
            get {
                if (methodTryParse == null) {
                    methodTryParse = GetParserMethod();
                }
                return methodTryParse;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        static Delegate_TryParse<T> GetParserMethod() {
            var typeT = typeof(T);
            var paramTypes = new Type[] { typeof(string), typeT.MakeByRefType() };
            var methodInfo = typeT.GetMethod("TryParse", BindingFlags.Static | BindingFlags.Public, null, paramTypes, null);
            if (methodInfo == null) {
                var message = String.Format("Unable to retrieve a 'TryParse' method for type '{0}'.", typeT.Name);
                throw new Exception(message);
            }
            return (Delegate_TryParse<T>) Delegate.CreateDelegate(typeof(Delegate_TryParse<T>), methodInfo);
        }
    }

答案 5 :(得分:0)

您列出的这些类型都有一个名为TryParse的静态方法。它们看起来很相似,但实际上签名完全不同。它们遵循类似的“模式”,但编译器无法检测到它。

您可能会尝试这样做:

    public T? ParseValue<T>(string value) where T : struct
    {
        if (typeof(T) == typeof(int))
        {
            int i;
            if (int.TryParse(value, out i))
                return (T)(object)i;
            return null;
        }
        if (typeof(T) == typeof(decimal)) 
        {
            decimal d;
            if (decimal.TryParse(value, out d))
                return (T)(object)d;
            return null;
        }
        // other supported types...
        throw new ArgumentException("Type not supported");
    }

然而,你不能。编译器无法知道(在编译时)如何将类型'T'转换为int(或任何其他类型)。

你可以进行双重投射以使其发挥作用。 (谢谢,Dotson)

用法:

        var mydecimal = ParseValue<decimal>("12.1");
        var myint = ParseValue<int>("-22");
        var badint = ParseValue<int>("Bad");
        // badint.HasValue == false

答案 6 :(得分:0)

实际上,您可以更新Matt的代码所做的事情并制作它,以下是代码:

enter code here:static T? TryParse<T>(string parse)
        where T : struct
    {
        Type t=typeof(T);
        if (t==typeof(int))
        {
            int i;
            if (int.TryParse(parse, out i))
                return (T)(object)i;
            return null;
            //Console.WriteLine(t.Name);
        }
        if (t == typeof(double))
        {
            double i;
            if (double.TryParse(parse, out i))
                return (T)(object)i;
            return null;
        }
        //blabla, more logic like datetime and other data types
        return null;
    }

而且,您可以像这样使用它: 双? i = TryParse(“111.111”); 诠释? a = TryParse(“111”);