如何编写通用扩展方法?

时间:2012-02-01 16:40:40

标签: c# generics c#-4.0

我正在开发一个围绕TryParse的通用包装器,如下所示:

    public delegate bool ParseDelegate<T>(string s, out T result);

    public static T? ParseOrNull<T>(this string value, ParseDelegate<T> parse) where T : struct
    {
        T result;
        var parsed = parse(value, out result);
        return parsed ? result : (T?)null;
    }

    [Test]
    public void ParsesValidInt()
    {
        Assert.AreEqual(1234, "1234".ParseOrNull<int>(int.TryParse));
    }

    [Test]
    public void ParsesValidDecimal()
    {
        Assert.AreEqual(12.34M, "12.34".ParseOrNull<decimal>(decimal.TryParse));
    }

这有点重复。有没有办法避免提及int.TryParse,所以我的调用如下所示:

"1234".ParseOrNull<int>()

6 个答案:

答案 0 :(得分:6)

  

有没有办法避免提及int.TryParse,所以我的调用看起来如下:

不直接,因为TryParse不是共享接口的一部分。如果存在这些值类型的共享接口,则可以通过约束来实现。


就个人而言,我不会建议使用扩展方法。我宁愿把它写成更像的东西:

public static class Parse
{
    public delegate bool ParseDelegate<T>(string s, out T result);
    public static T? FromString<T>(string value, ParseDelegate<T> parse) where T : struct
    {
        T result;
        var parsed = parse(value, out result);
        return parsed ? result : (T?)null;
    }
    public static int? ToNullableInt32(string value)
    {
        return FromString<int>(value, int.TryParse);
    }
    public static double? ToNullableDouble(string value)
    {
        return FromString<double>(value, double.TryParse);
    }
}

这预先增加了一些开销,但允许你非常干净地写这些,即:

    int? first = Parse.FromString<int>("1234", int.TryParse);
    int? second = Parse.ToNullableInt32("1234");
    double? third = Parse.ToNullableDouble("1234");

我认为放置扩展方法没什么价值,特别是像string(在任何地方都使用)这样的东西,因为它“污染”了字符串本身的编译。你会在使用字符串的任何地方看到这一点 - 基本上,每当你使用这个命名空间时,你最终会在你的intellisense等中使用这些解析方法。此外,这似乎更像是一个“实用程序”而不是应该出现的东西作为字符串本身的内置功能,这就是为什么我个人更喜欢单独的类。

答案 1 :(得分:3)

简而言之,不过你可以添加一个新的辅助方法:

public static int? ParseInt(this string value)
{
  return value.ParseOrNull<int>(int.TryParse);
}

然后:

"1234".ParseInt();

答案 2 :(得分:2)

了解Microsoft如何处理多种类型。它们为每种类型提供了一种方法。 Enumerable.Sum Method就是一个很好的例子。如果要简化调用代码,则应为每种类型提供重载:

public static int? ParseOrNull<int>(this string value) 
{
    int result;
    var parsed = int.TryParse(value, out result);
    return parsed ? result : (T?)null;
}
public static long? ParseOrNull<long>(this string value) 
{
    long result;
    var parsed = long.TryParse(value, out result);
    return parsed ? result : (T?)null;
}
// same for ulong, long, uint, ushort, short, byte, 
// bool, float, double, decimal. Do I forget one ?

我认为简化调用比方法本身更重要。事实上,没有太多类型可以处理。

答案 3 :(得分:1)

答案是肯定的。你试图在你要转换的类型上利用静态T.TryParse(string,out T)函数的存在,我们可以通过一点点反射很容易地做到这一点。

public static T? ParseOrNull<T>(this string str)
    where T: struct, IConvertible
{
    // find the TryParse method.
    var parseMethod = typeof(T).GetMethod("TryParse", 
                                    // We want the public static one
                                    BindingFlags.Public | BindingFlags.Static,
                                    Type.DefaultBinder,
                                    // where the arguments are (string, out T)
                                    new[] { typeof(string), typeof(T).MakeByRefType() },
                                    null);
    if (parseMethod == null)
        // You need to know this so you can parse manually
        throw new InvalidOperationException(
                            string.Format("{0} doesn't have a TryParse(..) function!",
                                                    typeof(T).FullName));
    // create the parameter list for the function call
    var args = new object[] { str, default(T) };
    // and then call the function.
    if ( (bool)parseMethod.Invoke(null, args))
        return (T?)args[1]; // if it returned true
    // if it returned false
    return null;
}

这是我提供的原始答案,基于您需要两种不同的解析方法:一种用于值类型,另一种用于引用类型。

    public delegate bool ParseDelegate<T>(string s, out T result);
    public static T? ParseOrNull<T>(this string str, ParseDelegate<T> Parse)
        where T: struct
    {
        T result;
        if (!Parse(str, out result))
            return null;
        return result;
    }

    public static T ParseOrNull<T>(this string str, ParseDelegate<T> Parse)
        where T : class
    {
        T result;
        if (!Parse(str, out result))
            return null;
        return result;
    }

答案 4 :(得分:1)

,您可以使用Convert.ChangeType

public static T? ParseOrNull<T>(this string value) where T : struct, IConvertible
{
    try
    {           
        return (T)Convert.ChangeType(value, typeof(T));
    }
    catch (FormatException ex)
    {
        return null;
    }
}

它不会像TryParse一样具有良好的性能(使用try catch),但应该适用于所有IConvertible类型

答案 5 :(得分:0)

public static T? ParseOrNull<T>(this string value)
            where T : struct
        {
            T result = default(T);

            object[] parameters = new object[] { value, result };
            foreach (System.Reflection.MethodInfo method in
                typeof(T).GetMethods()
                .Where(method => method.Name == "TryParse")
                .Where(method => method.GetParameters().Length == 2) //as opposed to the 4 argument version
                .Take(1) //shouldn't be needed, but just in case
                )
            {
                method.Invoke(null, parameters);
            }

            return (T)parameters[1];
        }

正如里德所提到的,我宁愿不使用字符串的扩展方法。我只使用Parser.Parse(字符串值)。虽然容易修复,但只需删除'this'即可。