简体   繁体   English

C#中TryParse()的通用包装器

[英]Generic wrapper for TryParse() in C#

I'm using the following methods to provide quick, inline access to the TryParse() method of various Type classes. 我正在使用以下方法提供对各种Type类的TryParse()方法的快速内联访问。 Basically I want to be able to parse a string coming from a web service if possible or return a default value if not. 基本上我希望能够解析来自Web服务的字符串(如果可能),否则返回默认值。

private Int64 Int64Parse(string value) {
    Int64 result;
    if (!Int64.TryParse(value, out result)) { return default(Int64); }
    return result;
}

private DateTime DateTimeParse(string value) {
    DateTime result;
    if (!DateTime.TryParse(value, out result)) { return default(DateTime); }
    return result;
}

private Decimal DecimalParse(string value) {
    Decimal result;
    if (!Decimal.TryParse(value, out result)) { return default(Decimal); }
    return result;
}

These are extremely repetitive, suggesting, to me, that there may be a way to wrap them into a single generic method. 这些是非常重复的,对我来说,可能有一种方法可以将它们包装成单个通用方法。

I'm stuck at the following but not sure how to proceed or how to search for how to proceed. 我坚持以下但不确定如何继续或如何搜索如何继续。

private T ParseString<T>(string value) {
    T result;
    if (!T.TryParse(value, out result)) { return default(T); }
    return result;
}

Any help would be appreciated. 任何帮助,将不胜感激。 Thanks. 谢谢。

==Edit== To add some context. ==编辑==添加一些上下文。 This is for a listener receiving postbacks from a specific credit card billing company. 这是为了收听来自特定信用卡结算公司的回发的听众。 I'm not doing validation at this step because that's being done in the business rules steps later. 我没有在此步骤进行验证,因为这是在稍后的业务规则步骤中完成的。 For example, I don't care if bank_batch_number comes in as an int, string or freeze-dried rodent; 例如,我不在乎bank_batch_number是作为int,string还是冻干的啮齿动物进入; I'm not going to halt with an exception if I can't cleanly log a field I don't use. 如果我不能干净地记录我不使用的字段,我不会停止异常。 I do care that ext_product_id exists in our DB and has a price matching currency_amount_settled in the message; 我关心ext_product_id存在于我们的数据库中,并且在消息中具有与currency_amount_settled相匹配的价格; if that test fails then the transaction is put on hold, a warning is logged, and our CS staff and myself will be alerted. 如果该测试失败,则交易被暂停,将记录警告,我们的CS员工和我自己将收到警报。

The culture thing mentioned below is sage advice though. 下面提到的文化事物是圣人的建议。

No, the methods are basically entirely separate - the compiler doesn't know that DateTime.TryParse is similar in any way to Int64.TryParse etc. 不,这些方法基本上是完全独立的 - 编译器不知道DateTime.TryParseInt64.TryParse等有任何Int64.TryParse

You could create a Dictionary<Type, Delegate> to map from the target type to the method, and then write something like: 可以创建一个Dictionary<Type, Delegate>来从目标类型映射到方法,然后编写如下内容:

private delegate bool Parser<T>(string value, out T result);

private T Parse<T>(string value) where T : struct
{
    // TODO: Validate that typeof(T) is in the dictionary
    Parser<T> parser = (Parser<T>) parsers[typeof(T)];
    T result;
    parser(value, out result);
    return result;
}

You can populate the dictionary like this: 您可以像这样填充字典:

static readonly Dictionary<Type, Delegate> Parsers = CreateParsers();    

static Dictionary<Type, Delegate> CreateParsers()
{
    var parsers = new Dictionary<Type, Delegate>();
    AddParser<DateTime>(parsers, DateTime.TryParse);
    AddParser<Int64>(parsers, Int64.TryParse);
    return parsers;
}

static void AddParser<T>(Dictionary<Type, Delegate> parsers, Parser<T> parser)
{
    parsers[typeof(T)] = parser;
}

Note that the TryParse pattern states that the value of the out parameter will be the default value for that type anyway, so you don't need your conditional logic. 请注意, TryParse模式表明out参数的值无论如何都是该类型的默认值,因此您不需要条件逻辑。 That means even your repetitive methods can become simpler: 这意味着即使您的重复方法也可以变得更简单:

private static Decimal DecimalParse(string value) {
    Decimal result;
    Decimal.TryParse(value, out result);
    return result;
}

As an aside, note that by default the TryParse pattern will use the thread's current culture. 另外,请注意,默认情况下, TryParse模式将使用线程的当前文化。 If this is being used to parse incoming data from a web service, I'd strongly recommend that you use the invariant culture instead. 如果这是用于解析来自Web服务的传入数据,我强烈建议您使用不变文化。 (Personally I wouldn't silently ignore bad data either, but I assume that's deliberate.) (我个人也不会默默地忽略不良数据,但我认为这是故意的。)

public delegate bool TryParseDelegate<T>(string str, out T value);

public static T ParseOrDefault<T>(string str, TryParseDelegate<T> parse)
{
    T value;
    return parse(str, out value) ? value : default(T);
}

You can call it like: 你可以这样称呼它:

long l = ParseOrDefault<long>("12345", long.TryParse);

Why not just use a simple extension method? 为什么不使用简单的扩展方法?

Jon Skeet's answer about just using the default result from the various TryParse methods is good. Jon Skeet关于仅使用各种TryParse方法的默认结果的答案是好的。 There is still a nice aspect of the extension methods, though. 还有的扩展方法一个好的方面,虽然。 If you are doing this a lot, you can accomplish the same thing in the calling code (plus optionally specifying an explicit default) in one line of code rather than three. 如果你这么做很多,你可以在一行代码而不是三行代码中完成相同的调用代码(加上可选择指定显式默认值)。

-- EDIT -- I do realize that in my original answer I basically just provided a slightly different way of doing the same thing the author was already doing. - 编辑 -确实意识到,在我原来的答案中,我基本上只提供了一种略微不同的方式来做同样的事情,作者已经在做了。 I caught this earlier today when I was real busy, thought the delegate and custom parser stuff looked like it might be a bit much, then cranked out an answer without really taking the time to completely understand what the question was. 我这个今天早些时候抓时,我是真正的忙,想委托和自定义分析器的东西看起来像它可能是一个有点多,然后手摇没有真正花时间来完全理解的问题是什么的答案。 Sorry. 抱歉。

How about the following, which uses an (overloaded) extension method and reflection? 如何使用(重载)扩展方法和反射? Refer to https://stackoverflow.com/a/4740544/618649 请参阅https://stackoverflow.com/a/4740544/618649

Caveat Emptor: my example does not account for you trying to convert types that do not have a TryParse method. 警告Emptor:我的例子没有说明你试图转换没有TryParse方法的类型。 There should be some exception handling around the GetMethod call, and so on. GetMethod调用应该有一些异常处理,依此类推。

/* The examples generates this output when run:

0
432123
-1
1/1/0001 12:00:00 AM
1/1/1970 12:00:00 AM
1/30/2013 12:00:00 PM
-1
12342.3233443

*/


class Program
    {
    static void Main ( string[] args )
        {
        Debug.WriteLine( "blah".Parse<Int64>() );
        Debug.WriteLine( "432123".Parse<long>() );
        Debug.WriteLine( "123904810293841209384".Parse<long>( -1 ) );

        Debug.WriteLine( "this is not a DateTime value".Parse<DateTime>() );
        Debug.WriteLine( "this is not a DateTime value".Parse<DateTime>( "jan 1, 1970 0:00:00".Convert<DateTime>() ) );
        Debug.WriteLine( "2013/01/30 12:00:00".Parse<DateTime>() );

        Debug.WriteLine( "this is not a decimal value".Parse<decimal>( -1 ) );
        Debug.WriteLine( "12342.3233443".Parse<decimal>() );
        }
    }

static public class Extensions
    {
    static private Dictionary<Type,MethodInfo> s_methods = new Dictionary<Type, MethodInfo>();

    static public T Parse<T> ( this string value ) where T : struct
        {
        return value.Parse<T>( default( T ) );
        }

    static public T Parse<T> ( this string value, T defaultValue ) where T : struct
        {
        // *EDITED* to cache the Reflection lookup--NOT thread safe
        MethodInfo m = null;
        if ( s_methods.ContainsKey( typeof( T ) ) )
            {
            m = s_methods[ typeof( T ) ];
            }
        else
            {
            m = typeof( T ).GetMethod(
                 "TryParse"
                 , BindingFlags.Public | BindingFlags.Static
                 , Type.DefaultBinder
                 , new[] { typeof( string ), typeof( T ).MakeByRefType() }
                 , null
                 );
            s_methods.Add( typeof( T ), m );
            }

        var args = new object[] { value, null };
        if( (bool)m.Invoke( null, args ))
            {
            return (T) args[ 1 ];
            }
        return defaultValue;
        }
    }

This is a method I sometimes use. 这是我有时使用的方法。 If performance is a major concern then this is not the way to go, as the try-catch block and the call to ChangeType are going to slow you down a good bit more than a type-specific TryParse. 如果性能是一个主要问题,那么这不是一种可行的方法,因为try-catch块和对ChangeType的调用会比特定类型的TryParse慢一点。

public static bool TryFromString<T>(string value, out T convertedValue)
{
    Type t = typeof(T);
    convertedValue = default(T);
    if (t.Name == "Nullable`1")
        t = System.Nullable.GetUnderlyingType(t);
    if (value != null)
    {
        try
        {
            convertedValue = (T)System.Convert.ChangeType(value, t, CultureInfo.CurrentCulture);
            return true;
        }
        catch
        {
        }
    }
    return false;
}

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

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