简体   繁体   English

如何编写通用扩展方法?

[英]How to write generic extension methods?

I am developing a generic wrapper around TryParse, as follows: 我正在开发一个围绕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));
    }

This is kinda repetitive. 这有点重复。 Is there a way to avoid mentioning int.TryParse at all, so that my calls look as follows: 有没有办法避免提及int.TryParse,所以我的调用如下所示:

"1234".ParseOrNull<int>()

Is there a way to avoid mentioning int.TryParse at all, so that my calls look as follows: 有没有办法避免提及int.TryParse,所以我的调用如下所示:

Not directly, as TryParse isn't part of a shared interface. 不直接,因为TryParse不是共享接口的一部分。 If there were a shared interface to these value types, this would be possible via a constraint. 如果存在这些值类型的共享接口,则可以通过约束来实现。


Personally, I would not suggest using extension methods for this. 就个人而言,我不会建议使用扩展方法。 I would rather write this as something more like: 我宁愿把它写成更像的东西:

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

This adds a bit of overhead up front, but allows you to write these very cleanly, ie: 这预先增加了一些开销,但允许你非常干净地写这些,即:

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

I see little value in putting an extension method, especially on something like string (which is used everywhere), as it "pollutes" the compilation of string itself. 我认为放置扩展方法没有什么价值,特别是在string (它随处可用)之类的东西上,因为它“污染”了字符串本身的编译。 You'll see this everywhere you use strings - basically, any time you use this namespace, you'll end up having these parse methods in your intellisense, etc. In addition, this seems more like a "utility" than something that should appear as built-in functionality of string itself, which is why I personally prefer a separate class for it. 你会在使用字符串的任何地方看到这一点 - 基本上,每当你使用这个命名空间时,你最终会在你的intellisense等中使用这些解析方法。此外,这似乎更像是一个“实用程序”而不是应该出现的东西作为字符串本身的内置功能,这就是为什么我个人更喜欢它的单独类。

In short no but you can add a new helper method: 总之没有,但你可以添加一个新的帮助方法:

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

and then: 接着:

"1234".ParseInt();

Look at how Microsoft deals with several types . 看看Microsoft如何处理几种类型。 They provides one method for each type. 它们为每种类型提供了一种方法。 Enumerable.Sum Method is a good example. Enumerable.Sum方法就是一个很好的例子。 If you want to simplify the calling code, you should provide the overloads for each types : 如果要简化调用代码,则应为每种类型提供重载:

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 ?

I think it's more important to simplify calls than the method itself. 我认为简化调用比方法本身更重要。 In fact, there is not a huge number of types to deal with. 事实上,没有大量的类型可以处理。

The answer is a big YES. 答案很重要。 You're trying to exploit the existence of the static T.TryParse(string, out T) function on the types you're converting to, and we can do that pretty easily with a little reflection. 你试图在你要转换的类型上利用静态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;
}

This is the original answer I provided, based on the idea that you need two different parse methods: One for value types and another for reference types. 这是我提供的原始答案,基于您需要两种不同的解析方法:一种用于值类型,另一种用于引用类型。

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

Yes , you can use Convert.ChangeType 是的 ,您可以使用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;
    }
}

It wont have as good performance (use of try catch) as TryParse , but should work for all IConvertible types 它不会像TryParse那样具有良好的性能(使用try catch),但应该适用于所有IConvertible类型

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

As Reed mentions, I'd rather not use an extension method of string. 正如Reed所提到的,我宁愿不使用字符串的扩展方法。 I'd just use Parser.Parse(string value). 我只使用Parser.Parse(字符串值)。 Easy fix though, just remove the 'this' and voila. 虽然容易修复,但只需删除'this'即可。

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

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