简体   繁体   English

泛型和可空类型

[英]Generics and nullable type

Say I have a method that takes an int as a string and returns the int if the parse succeeds or a null value otherwise. 假设我有一个方法,将int作为字符串,如果解析成功则返回int,否则返回null值。

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

How can this method be re-written so that it works not only with int?, but also long?, decimal? 如何重写这个方法,使它不仅可以用于int?,而且还可以用long ?, decimal? and DateTime? 和日期时间? ?

It's funny you should mention it because I was messing around with something just like this the other day: 你应该提一下这很有趣,因为前几天我正在搞乱这样的事情:

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

It is a similar approach but think of it like a better TryParse method that returns a Tuple<Boolean, T?> (this requires .NET 4). 这是一种类似的方法,但想到它就像一个更好的TryParse方法返回一个Tuple<Boolean, T?> (这需要.NET 4)。 The first property of the tuple is a boolean value indicating the success or failure of the parsing attempt and the second property is a nullable value typed to the generic type argument that will be null if parsing fails and the value if parsing succeeds. 元组的第一个属性是指示解析尝试成功或失败的布尔值,第二个属性是类型为泛型类型参数的可空值,如果解析失败则为null ,如果解析成功,则为null

It works by using reflection to retrieve a static Parse(String) method from the generic type argument and invokes that method for the string that is passed in. I built it as an extension method to allow you to do stuff like this: 它的工作原理是使用反射从泛型类型参数中检索静态Parse(String)方法,并为传入的字符串调用该方法。我将其构建为扩展方法,允许您执行以下操作:

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

Unfortunately this won't work on enums since they don't have the same signature for the parse method so you couldn't use this extension to parse an enum but it wouldn't be hard to hack this up to make a special case for enums. 不幸的是,这对enums不起作用,因为它们没有相同的parse方法签名,所以你不能使用这个扩展来解析enum但是要破解它就不难做出特殊情况枚举。

One of the nice things about this approach is that the cost of retrieving the Parse method via reflection is only incurred on the first use since a static delegate is created for all subsequent uses. 这种方法的一个Parse是,通过反射检索Parse方法的成本仅在第一次使用时产生,因为为所有后续使用创建了静态委托。


One more thing - the only thing that is clunky about this approach is that there is no language extensions or syntactic sugar that would make this easy to work with. 还有一件事 - 这种方法唯一笨拙的是,没有语言扩展或语法糖可以使这很容易使用。 What I was hoping to achieve with this code was a less clunky way of using the standard TryParse methods that exist in the BCL. 我希望用这个代码实现的是使用BCL中存在的标准TryParse方法的一种不那么笨重的方式。

I personally find this pattern rather ugly: 我个人认为这种模式相当难看:

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

mainly because it requires a variable declaration ahead of time and the use of an out parameter. 主要是因为它需要提前声明变量并使用out参数。 My approach above isn't really that much better: 我上面的方法并没有那么好:

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

What would be really cool would be to see a C# language extension that was built to work with a Tuple<Boolean, T?> that would allow us to work with this type smoothly but I get the feeling the more I write about this that it doesn't really seem that feasible. 真的很酷的是看到一个C#语言扩展,它是为了使用Tuple<Boolean, T?>而构建的,它允许我们顺利地使用这种类型,但我感觉我写的更多关于它看起来并不可行。

Instead of using the question mark, you can explicitly use the Nullable keyword: for example, 您可以显式使用Nullable关键字,而不是使用问号:例如,

int? equals Nullable<int> 等于Nullable<int>

Therefore switching your original design to Nullable<T> ParseValue(string valueAsString ) should do the trick: just do the generic implementation after this. 因此,将原始设计切换为Nullable<T> ParseValue(string valueAsString )应该可以解决这个问题:在此之后只需执行通用实现。

It is best to implement an Extension method and you can even parse Enumerations. 最好实现Extension方法,甚至可以解析Enumerations。 This way you can get a Nullable<ForAnyValueType> like this: 这样你可以像这样得到一个Nullable <ForAnyValueType>:

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

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

I don't really see why use the Tuple in Andrews solution, as long as we're returning a Nullable anyway, seems like doing the same thing twice. 我真的不明白为什么在安德鲁斯的解决方案中使用Tuple,只要我们无论如何都要返回Nullable,似乎两次做同样的事情。 I edited his solution to use TryParse and allow to return either a Nullable or some default value specified as argument. 我编辑了他的解决方案以使用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);
        }
    }

These types that you have listed all have a static method called TryParse. 您列出的这些类型都有一个名为TryParse的静态方法。 They look similar, but in fact the signatures are completely different. 它们看起来很相似,但实际上签名完全不同。 They follow a similar 'pattern', but that is not detectable by the compiler. 它们遵循类似的“模式”,但编译器无法检测到。

You might try to do something like this: 您可能会尝试执行以下操作:

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

However, you can't. 但是,你做不到。 The compiler has no way to know (at compile time) how to convert type 'T' to an int (or any other type). 编译器无法知道(在编译时)如何将类型'T'转换为int(或任何其他类型)。

You can double-cast to make this work. 您可以进行双重投射以使其工作。 (Thanks, Dotson) (谢谢,Dotson)

Usage: 用法:

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

Actually, You can update what Matt's code has done and make it, and here is the code: 实际上,你可以更新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;
    }

And also, you can use it like this: double? 而且,你可以像这样使用它:双? i = TryParse("111.111"); i = TryParse(“111.111”); int? 诠释? a = TryParse("111"); a = TryParse(“111”);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM