【问题标题】:how to use <T>.TryParse in a generic Method while T is either double or Int如何在 T 为 double 或 Int 时在泛型方法中使用 <T>.TryParse
【发布时间】:2012-05-13 19:19:31
【问题描述】:

在我的一个项目中,我使用了以下两种方法。 1. GetDoubleValue 和 2. GetIntValue。 GetDoubleValue 使用 double.TryParse 参数 str 字符串,如果失败则返回 0,而 GetIntValue 尝试 int.TryParse 参数 str 字符串,如果失败则返回 0。我想要的是将这两种方法组合成一个通用方法,该方法与字符串 str 一起接收参数 T ,这样如果我想使用 GetDoubleValue 方法,我可以使用 double 作为参数 T ,如果我想使用 GetIntValue 方法,我可以使用参数 T 的 int

public double GetDoubleValue(string str)
{
    double d;
    double.TryParse(str, out d);
    return d;
}
public int GetIntValue(string str)
{
    int i;
    int.TryParse(str, out i);
    return i;
}

注意:我已经尝试过这样的事情;

private T GetDoubleOrIntValue<T>(string str) where T : struct 
{
    T t;
    t.TryParse(str, out t);
    return t;
}

编辑

在我的数据库中,我在具有数字数据类型的不同表中有 30 多个列。如果用户没有在文本框中输入任何内容,即他将所有或部分文本框留空,我想在每列中插入 0。如果我不使用 GetIntValue 方法,我将不得不使用方法体超过 30 次。这就是为什么我通过方法方法来做到这一点。例如,我正在写三十多个示例中的三个

cmd.Parameters.Add("@AddmissionFee", SqlDbType.Decimal).Value = GetIntValue(tbadmissionfee.Text);
cmd.Parameters.Add("@ComputerFee", SqlDbType.Decimal).Value = GetIntValue(tbcomputerfee.Text);
cmd.Parameters.Add("@NotesCharges", SqlDbType.Decimal).Value = GetDoubleValue(tbnotescharges.Text);

我想将上述两种方法结合起来,因为今天我有两种这样的方法,如果结合起来不会给编程带来任何更好的改进,但明天我可能会有数十种这样的方法,它们会更好地组合成一个通用方法。例如,我可能有 GetInt32Value、GetShortValue 等。希望现在清楚我为什么要这个???

【问题讨论】:

  • 使用当前的泛型类型约束,这是不可能的。
  • 你打算怎么称呼这个泛型方法?
  • 您确定要在解析失败时让此函数静默返回默认值吗?
  • 我写了一个generic parser helper。寻找带有反射的TryParse 方法,并缓存委托以获得高性能。
  • 我用谷歌找到了一篇相关文章:geekswithblogs.net/michelotti/archive/2008/10/05/…

标签: c# generics


【解决方案1】:

我会考虑写一个扩展或静态方法:

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

public static T GetValue<T>(this string str, TryParse<T> parseFunc)
{
    T val;
    parseFunc(str, out val);
    return val;
}

然后,您必须提供要使用的 TryParse 实现:

int i = "1234".GetValue<int>(int.TryParse);

请注意,如果解析失败,此函数会静默返回默认值。如果 TryParse 委托返回 false,您可能希望返回 default(T)

【讨论】:

  • @svick - 这比为您要解析的每种类型创建一个函数具有更少的重复。不过,如果您只需要处理 ints 和 doubles 可能就有点过头了。
  • +1 这里展示了这种方法:geekswithblogs.net/michelotti/archive/2008/10/05/… 我同意在这种情况下它可能是矫枉过正,但作为一个通用的实用程序类它可能是有用的。
【解决方案2】:

我同意马克拜尔斯的观点。尝试使此方法通用可能不是一个好主意。一点点代码重复不会有什么坏处(只要它真的只是一点点)。您可以将任何struct 与您的通用版本一起使用这一事实对我来说也不是一个好主意。

如果你真的想这样做,你可以尝试使用反射(就像 Minustar 建议的那样),但这既丑陋又慢。

相反,您可以使用Convert.ChangeType():

private T GetValue<T>(string str) where T : struct 
{
    return (T)Convert.ChangeType(str, typeof(T));
}

【讨论】:

  • 感谢 ChangeType
  • 我们需要尝试ChangeType
【解决方案3】:

你可以这样做:

   public static T GetDoubleOrIntValue<T>(this string str) where T : IConvertible
{
    var thisType = default(T);
    var typeCode = thisType.GetTypeCode();
    if (typeCode == TypeCode.Double)
    {
        double d;
        double.TryParse(str, out d);
        return (T)Convert.ChangeType(d,typeCode) ;
    }
    else if (typeCode == TypeCode.Int32)
    {
        int i;
        int.TryParse(str, out i);
        return (T)Convert.ChangeType(i, typeCode);
    }
    return thisType;
}

那么当你调用它时:

string d = "1.1";
string i = "3";

double doubleValue = d.GetDoubleOrIntValue<double>();
int intValue = i.GetDoubleOrIntValue<int>();

但整件事对我来说似乎有点傻。

编辑:看到其他人使用 Convert.ChangeType...它提供了一个通用的返回类型。

【讨论】:

  • 感谢 Eric Dahlvang。您提供的代码足够大,可以替代 Mark Byers 建议的上述两种方法
  • 我今天思考了你的编码。对于我的问题,这确实不是一个糟糕的解决方案。非常感谢。
  • 我必须说这真的很糟糕,尽管我很喜欢 c# 我想要一个 convert.try(type int,object value)
【解决方案4】:

所以不要写:

double d = GetDoubleValue(str);

你想写这个吗?

double d = GetValue<double>(str);

即使你可以让它工作,有什么好处?我个人认为这对客户来说并不是一个巨大的进步。唯一的优点是不必两次实施相同的方法。但考虑到方法的简单性和实现此类代码重用的难度,这里只复制那几行代码似乎是合理的。

您并不孤单。看看 .NET 框架中适用于不同类型的其他方法,看看它们是如何解决的。以下是BinaryReader 允许您读取不同类型的方式:

这不是很漂亮,但通常是这样做的。


关于您的更新,我还有两点要说明。

您提到您可能有两种以上的类型,这可能会导致相同代码的更多重复。鉴于该方法非常简洁和简单,复制和粘贴代码似乎没有问题。在我看来,你不应该复制和粘贴代码是一个合理的例外。 here 方法减少了重复的数量,尽管您仍然需要在公共接口中使用许多类似命名的方法。

但是我认为重要的是要提到在您的情况下静默使用默认值似乎是错误的,正如 Lee 在评论中已经提到的那样。如果用户在输入数据时出错,他们应该会收到一条错误消息。您应该使用某种验证框架来确保字符串有效,并在它们无效时通知您的用户问题出在哪里。一旦您验证了所有内容,就可以安全地使用int.Parse 而不是int.TryParse,因为您知道解析会成功。如果解析确实失败,那是一种异常情况(验证中的错误),因此应用程序因异常退出并记录堆栈跟踪似乎是公平的。这将帮助您找到并修复错误。

【讨论】:

  • @Marks Byers 谢谢。你让我 100% 正确。嗯,所以没有办法做到这一点??????
  • @kashif:很难说什么是“正确”的解决方案,因为你没有说你为什么需要这个。为什么不直接使用 TryParse 而不是将其隐藏在方法调用后面? .NET 设计人员认真考虑了 TryParse 的 API,这就是他们想出的。您基本上只是更改 API,以便在解析失败时返回 0 而不是 false。这如何更好?如果你真的想要这个,让客户端选择默认值怎么样?示例:TryParse(string s, int defaultValue)TryParse(string s, double defaultValue)?
【解决方案5】:

您可以尝试使用反射:首先,尝试在 替换的类型上找到“TryParse”方法。

T t;
var typeInfo = typeof(T);
var method = typeInfo.GetMethod("TryParse", BindingFlags.Public | BindingFlags.Static);
var result = (bool)method.Invoke(null, new object[] { str, t });

if (result)
    return t;

此代码未经测试;它也不是最佳性能。

【讨论】:

  • 这行不通。 TryParse 方法修改了您传递给它的实例,它不是t,而是它的盒装副本。
  • 另外,还有一些其他问题(虽然没有那么大):您需要先初始化t,然后才能使用它;你需要指定你想要哪个TryParseint有两个);当resultfalse 时,你必须做点什么。
【解决方案6】:

我制作了一个简单的示例。代码不是最优的,但可以按预期工作。

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GetValue<int>("123"));
        Console.WriteLine(GetValue<double>("123.123"));
        Console.WriteLine(GetValue<DateTime>("2001-01-01 01:01:01"));
    }

    static T GetValue<T>(string s)
    {
        var tryParse = typeof (T).GetMethod(
            "TryParse", new [] {typeof(string), typeof(T).MakeByRefType()});
        if (tryParse == null)
            throw new InvalidOperationException();


        T t = default (T);
        var parameters = new object[] {s, t};
        var success = tryParse.Invoke(null, parameters);
        if ((bool) success) t = (T)parameters[1];
        return t;
    }
}

【讨论】:

  • 我宁愿使用GetMethod() 的重载来指定参数,而不是仅仅依赖参数的数量。如果你这样做了,别忘了使用MakeByRefType()
  • 这正是我想要的工作方式,但我宁愿使用上述两种方法而不是使用这个巨大的代码
【解决方案7】:

如您所见,您不能将where T 限制为DoubleInt32,这就是为什么您可能在那个地方有struct(int 和double 不是结构)。值类型不能作为泛型的限制。

所以你不能安全地输入 TryParse 方法。您应该对 T 进行类型检查,如果它是 double 或 int 则抛出异常,就像@ie 的回答一样。

我的建议是使用不同的签名:

private static bool GetValue(string str, out double result)
{
...    
}

private static bool GetValue(string str, out int result)
{
...    
}

您也可以引入一个接口IParsable。将doubleint 结果放入其中并实现隐式转换为双精度或整数。

【讨论】:

  • intdoublestructs,至少对于泛型类型约束而言。
【解决方案8】:

扩展方法呢?

public static class Extensions
{    
    public static Nullable<T> TryParse<T>(this String str) where T:struct
    {
        try
        {
            T parsedValue = (T)Convert.ChangeType(str, typeof(T));
            return parsedValue;
        }
        catch { return null; }
    }
}

用法:

int i = "123".TryParse<int>() ?? 0;

【讨论】:

    猜你喜欢
    • 2014-02-26
    • 1970-01-01
    • 1970-01-01
    • 2013-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-28
    • 1970-01-01
    相关资源
    最近更新 更多