【问题标题】:Generic wrapper for TryParse() in C#C# 中 TryParse() 的通用包装器
【发布时间】:2013-01-29 19:48:33
【问题描述】:

我正在使用以下方法来提供对各种 Type 类的 TryParse() 方法的快速内联访问。基本上,如果可能,我希望能够解析来自 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;
}

这些是非常重复的,对我来说,暗示可能有一种方法可以将它们包装成一个通用方法。

我被困在以下但不知道如何继续或如何搜索如何继续。

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

任何帮助将不胜感激。 谢谢。

==编辑== 添加一些上下文。这适用于从特定信用卡计费公司接收回发的听众。我没有在这一步进行验证,因为稍后会在业务规则步骤中进行验证。例如,我不在乎 bank_batch_number 是以 int、string 还是冻干啮齿动物的形式出现的;如果我不能干净地记录我不使用的字段,我不会因异常而停止。我确实关心我们的数据库中存在 ext_product_id 并且在消息中具有与 currency_amount_settled 匹配的价格;如果该测试失败,则交易将被搁置,并记录一个警告,并且我们的 CS 人员和我自己都会收到警报。

下面提到的文化是明智的建议。

【问题讨论】:

  • 您不能这样做,因为编译器无法确定T 类型是否会有TryParse() 方法。
  • 我认为唯一的选择是使用反射来获取对TryParse 方法的引用,无论T 是什么类型(如果有的话),并通过反射调用它。跨度>
  • @Dani:你会如何使用dynamic调用静态方法?

标签: c# generics


【解决方案1】:

不,这些方法基本上是完全独立的——编译器不知道DateTime.TryParseInt64.TryParse 等有任何相似之处。

可以创建一个Dictionary&lt;Type, Delegate&gt; 以从目标类型映射到方法,然后编写如下内容:

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

您可以像这样填充字典:

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

注意TryParse 模式声明out 参数的值无论如何都是该类型的默认值,因此您不需要条件逻辑。这意味着即使是重复的方法也可以变得更简单:

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

顺便说一句,请注意默认情况下TryParse 模式将使用线程的当前文化。如果这是用于解析来自 Web 服务的传入数据,我强烈建议您改用不变文化。 (我个人也不会默默地忽略不良数据,但我认为这是故意的。)

【讨论】:

  • 我会试一试,看看效果如何。在此步骤中忽略不良数据是有意的,因为此步骤是在原始日志记录之后和针对业务规则进行验证之前。此外,由于并非每个请求中都存在每个字段,因此实际上有几个字段会通过 [invoice.ReferenceInvoiceId = IntParse( Params.FirstOrDefault(p => p.Key == "ref_inv") 之类的空字符串进入解析器.Value);]
  • 对字典查找与其他解决方案的成本有何想法?
  • @Jon:与我对性能的正常想法相同:在您真正感兴趣的上下文中衡量替代方法。在一个上下文中微不足道的成本可能在不同的上下文中成为杀手。
【解决方案2】:

为什么不直接使用简单的扩展方法?

Jon Skeet 关于仅使用来自各种 TryParse 方法的默认结果的回答很好。不过, 仍然是扩展方法的一个不错的方面。如果你经常这样做,你可以在调用代码中用一行代码而不是三行代码完成同样的事情(加上可选地指定一个显式默认值)。

-- 编辑 --确实意识到,在我最初的回答中,我基本上只是提供了一种稍微不同的方式来做作者已经在做的事情。我今天早些时候发现了这个问题,当时我真的很忙,认为委托和自定义解析器的东西看起来可能有点多,然后在没有真正花时间完全理解问题的情况下给出了一个答案曾是。对不起。

以下如何使用(重载)扩展方法和反射?参考https://stackoverflow.com/a/4740544/618649

Caveat Emptor:我的示例没有说明您尝试转换没有 TryParse 方法的类型。 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;
        }
    }

【讨论】:

  • 尽管——Jon Skeet 所说的关于文化设置和忽略不良数据...
  • 我编辑了我的原始答案,现在它实际上回答了被问到的问题,而不是仅仅提供一种不同的方式来做同样的事情。过失。祝你好运!
  • 这看起来几乎正是我想要的。使用扩展方法方法比我使用静态类型方法更适合我当前的“lots o' linq”风格。
  • @SnowCrash:您真的想对每次转换都使用反射吗?这对我来说听起来不是一个好主意。显然,您仍然可以更改我的方法以使用扩展方法作为公共 API - 但我个人会坚持使用“注册一堆委托”方法进行实际转换。
  • 那肯定行得通,乔恩。我不嫉妒你的代表!不过,我欣然承认我真的很喜欢扩展方法。 :-) 我想你必须测试性能才能看到惩罚是什么。这可能是旧的 80/20 规则的一个例子。 ASP.NET MVC 在每个页面请求上都使用反射来实现其按约定操作的机制,并且似乎还可以。
【解决方案3】:
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);
}

你可以这样称呼它:

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

【讨论】:

    【解决方案4】:

    这是我有时使用的一种方法。如果性能是一个主要问题,那么这不是要走的路,因为 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;
    }
    

    【讨论】:

      猜你喜欢
      • 2011-10-12
      • 2012-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多