开发者

C# string Parsing to variable types

开发者 https://www.devze.com 2022-12-12 09:45 出处:网络
I want to parse a string into a type easily, but I don\'t want to write wrapper code for each type, I just want to be able to开发者_StackOverflow中文版 do \"1234\".Parse() or the like and have it retu

I want to parse a string into a type easily, but I don't want to write wrapper code for each type, I just want to be able to开发者_StackOverflow中文版 do "1234".Parse() or the like and have it return 1234. This should work for any type that has parsing capabilities.


This trick should work. It uses the type of the variable you're assigning to automagically:

public static class StringExtensions {
    public static ParsedString Parse(this string s) {
        return new ParsedString(s);
    }
}
public class ParsedString {
    string str;
    public ParsedString(string s) {
        str = s;
    }
    public static implicit operator int(ParsedString s) {
        return int.Parse(s.str);
    }
    public static implicit operator double(ParsedString s) {
        return double.Parse(s.str);
    }
    public static implicit operator short(ParsedString s) {
        return short.Parse(s.str);
    }
    public static implicit operator byte(ParsedString s) {
        return byte.Parse(s.str);
    }
    // ... add other supported types ...
}

Usage:

int p = "1234".Parse();

I would prefer explicitly parsing using framework provided methods rather than relying on these kind of tricks.


My solution works for any type that implements the static method TryParse(string, out T), whether it's a class or a struct. Also, it will work for nullable structs, e.g.

"1234".Parse<int>() == 1234
"asdf".Parse<int>() == 0 // i.e. default(int)
"1234".Parse<int?>() == 1234
"asdf".Parse<int?>() == null
"2001-02-03".Parse<DateTime?>() == new DateTime(2009, 2, 3)

and since System.Net.IPAddress has TryParse,

"127.0.0.1".Parse<IPAddress>().Equals(new IPAddress(new byte[] { 127, 0, 0, 1 }))

I create the code with the System.Linq.Expressions framework, and then cache the created lambda. Since this is done in a generic static class with a specified type, this happens only once per type to parse.

public static class StringExtensions
{
    /// <summary>
    /// Parse the <paramref name="target"/> as a <typeparamref name="T"/>. If this cannot be achieved, return the default value of <typeparamref name="T"/>.
    /// </summary>
    /// <typeparam name="T">The type to parse into.</typeparam>
    /// <param name="target">The string to parse.</param>
    /// <returns>The resultant <typeparamref name="T"/> or the default of <typeparamref name="T"/>.</returns>
    /// <example>
    /// <code>
    /// "1234".Parse&ltint&gt;() == 1234;
    /// "a".Parse&ltint&gt;() == 0;
    /// "a".Parse&ltint?&gt;() == null;
    /// "2010-01-01".Parse&lt;DateTime?&gt;() == new DateTime(2010, 1, 1)
    /// "2010-01-01a".Parse&lt;DateTime?&gt;() == null
    /// "127.0.0.1".Parse&lt;System.Net.IPAddress&gt;().Equals(new System.Net.IPAddress(new byte[] { 127, 0, 0, 1 }))
    /// "".Parse&lt;System.Net.IPAddress&gt;() == null
    /// </code>
    /// </example>
    public static T Parse<T>(this string target)
    {
        return ParseHelper<T>.Parse(target);
    }

    /// <summary>
    /// Parse the <paramref name="target"/> as a <typeparamref name="T"/>. If this cannot be achieved, return <paramref name="defaultValue"/>
    /// </summary>
    /// <typeparam name="T">The type to parse into.</typeparam>
    /// <param name="target">The string to parse.</param>
    /// <param name="defaultValue">The value to return if <paramref name="target"/> could not be parsed.</param>
    /// <returns>The resultant <typeparamref name="T"/> or <paramref name="defaultValue"/>.</returns>
    /// <example>
    /// <code>
    /// "1234".Parse&ltint&gt;(-1) == 1234;
    /// "a".Parse&ltint&gt;(-1) == -1;
    /// "2010-01-01".Parse&lt;DateTime?&gt;(new DateTime(1900, 1, 1)) == new DateTime(2010, 1, 1)
    /// "2010-01-01a".Parse&lt;DateTime?&gt;(new DateTime(1900, 1, 1)) == new DateTime(1900, 1, 1)
    /// "127.0.0.1".Parse&lt;System.Net.IPAddress&gt;(new System.Net.IPAddress(new byte[] { 0, 0, 0, 0 })).Equals(new System.Net.IPAddress(new byte[] { 127, 0, 0, 1 }))
    /// "".Parse&lt;System.Net.IPAddress&gt;(new System.Net.IPAddress(new byte[] { 0, 0, 0, 0 })).Equals(new System.Net.IPAddress(new byte[] { 0, 0, 0, 0 }))
    /// </code>
    /// </example>
    public static T Parse<T>(this string target, T defaultValue)
    {
        return ParseHelper<T>.Parse(target, defaultValue);
    }

    private static class ParseHelper<T>
    {
        private static readonly Func<string, T, T, T> _parser;

        static ParseHelper()
        {
            Type type = typeof(T);
            bool isNullable = false;
            if (type.GetGenericArguments().Length > 0 && type.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                isNullable = true;
                type = type.GetGenericArguments()[0];
            }

            ParameterExpression target = Expression.Parameter(typeof(string), "target");
            ParameterExpression defaultValue = Expression.Parameter(typeof(T), "defaultValue");
            ParameterExpression result = Expression.Parameter(typeof(T), "result");
            if (isNullable)
            {
                Type helper = typeof(NullableParseHelper<>);
                helper = helper.MakeGenericType(typeof(T), type);
                MethodInfo parseMethod = helper.GetMethod("Parse");
                _parser = Expression.Lambda<Func<string, T, T, T>>(
                    Expression.Call(parseMethod, target, defaultValue),
                    target, defaultValue, result).Compile();
            }
            else
            {
                MethodInfo tryParseMethod = (from m in typeof(T).GetMethods()
                                             where m.Name == "TryParse"
                                             let ps = m.GetParameters()
                                             where ps.Count() == 2
                                                && ps[0].ParameterType == typeof(string)
                                                && ps[1].ParameterType == typeof(T).MakeByRefType()
                                             select m).SingleOrDefault();

                if (tryParseMethod == null)
                {
                    throw new InvalidOperationException(string.Format("Cannot find method {0}.TryParse(string, out {0})", type.FullName));
                }
                _parser = Expression.Lambda<Func<string, T, T, T>>(
                    Expression.Condition(
                        Expression.Call(tryParseMethod, target, result),
                        result,
                        defaultValue),
                    target, defaultValue, result).Compile();
            }
        }

        public static T Parse(string target)
        {
            return _parser.Invoke(target, default(T), default(T));
        }

        public static T Parse(string target, T defaultValue)
        {
            return _parser.Invoke(target, defaultValue, default(T));
        }

        private static class NullableParseHelper<TBase> where TBase : struct
        {
            private static readonly Func<string, TBase?, TBase, TBase?> _parser;

            static NullableParseHelper()
            {
                MethodInfo tryParseMethod = (from m in typeof(TBase).GetMethods()
                                             where m.Name == "TryParse"
                                             let ps = m.GetParameters()
                                             where ps.Count() == 2
                                                && ps[0].ParameterType == typeof(string)
                                                && ps[1].ParameterType == typeof(TBase).MakeByRefType()
                                             select m).SingleOrDefault();

                if (tryParseMethod == null)
                {
                    throw new InvalidOperationException(string.Format("Cannot find method {0}.TryParse(string, out {0})", typeof(TBase).FullName));
                }
                ParameterExpression target = Expression.Parameter(typeof(string), "target");
                ParameterExpression defaultValue = Expression.Parameter(typeof(TBase?), "defaultValue");
                ParameterExpression result = Expression.Parameter(typeof(TBase), "result");
                _parser = Expression.Lambda<Func<string, TBase?, TBase, TBase?>>(
                    Expression.Condition(
                        Expression.Call(tryParseMethod, target, result),
                        Expression.ConvertChecked(result, typeof(TBase?)),
                        defaultValue),
                    target, defaultValue, result).Compile();
            }

            public static TBase? Parse(string target, TBase? defaultValue)
            {
                return _parser.Invoke(target, defaultValue, default(TBase));
            }
        }
    }
}

pants!


Why can't you use the parsing already available?

int.Parse("1234");
decimal.Parse("1234");
double.Parse("1234");

Or use TryParse if you're not sure it will be able to successfully parse.

Or if you wanted to implement it as an extension method, take a look at this article that will show you how to create a Generic String.Parse method.

Edit: I have no idea how that site went down so quickly after I posted my answer. Here is the class that the article created:

using System;
using System.ComponentModel;

public static class Parser {

     public static T Parse<T>(this string value) {
        // Get default value for type so if string
        // is empty then we can return default value.
        T result = default(T);

        if (!string.IsNullOrEmpty(value)) {
          // we are not going to handle exception here
          // if you need SafeParse then you should create
          // another method specially for that.
          TypeConverter tc = TypeDescriptor.GetConverter(typeof(T));
          result = (T)tc.ConvertFrom(value);
        }

        return result;
    }
}

Examples:

// regular parsing
int i = "123".Parse<int>(); 
int? inull = "123".Parse<int?>(); 
DateTime d = "01/12/2008".Parse<DateTime>(); 
DateTime? dn = "01/12/2008".Parse<DateTime?>();

// null values
string sample = null;
int? k = sample.Parse<int?>(); // returns null
int l = sample.Parse<int>();   // returns 0
DateTime dd = sample.Parse<DateTime>(); // returns 01/01/0001
DateTime? ddn = sample.Parse<DateTime?>(); // returns null


I know this question is four years old, but still you could consider using this:

public static T Parse<T>(this string target)
{
    Type type = typeof(T);

    //In case of a nullable type, use the underlaying type:
    var ReturnType = Nullable.GetUnderlyingType(type) ?? type;

    try
    {
        //in case of a nullable type and the input text is null, return the default value (null)
        if (ReturnType != type && target == null)
            return default(T);

        return (T)Convert.ChangeType(target, ReturnType);
    }
    catch
    {
        return default(T);
    }
}


You can do this with a series of .TryParse() if blocks, but you won't be able to do much with it, since this method will have to return type object. So at the call site, you'll just have to attempt to cast it before doing any arithmetic or anything anyway.


You could write a wrapper function that calls tryParse for each type that you want to support.


There are two problems with that scenario. First you would have to write some code to analyse the string to try to determine what data types it would be possible to parse into, and then some logic to choose one of them. (The string "1" for example would be parsable to byte, sbyte, int, uint, long, ulong, float, double and Decimal. Even worse, the string "4.8.12" would be parsable to several numeric types as well as DateTime in three different ways resulting in totally different values...)

The other problem is that any method capable of doing that would have to return the value boxed in an object, so you would still need wrapper code for each data type just to unbox the value.

Besides, what would you do with a value where you have no control over the type? So, don't try to make this simpler, it only gets a lot more complicated.

0

精彩评论

暂无评论...
验证码 换一张
取 消