generics和可空类型

假设我有一个方法,将int作为字符串,如果解析成功则返回int,否则返回null值。

int? ParseValue(string intAsString) { int i; if (int.TryParse(intAsString, out i)) return i; return null; } 

如何重写这个方法,使它不仅可以用于int?,还可以用long ?, decimal? 和日期时间? ?

你应该提一下这很有趣,因为前几天我正在搞这样的事情:

 using System; using System.Reflection; static class Example { public static Tuple TryParse(this String candidate) where T : struct { T? value = null; Boolean success = false; var parser = ParsingBinder.GetParser(); try { value = parser(candidate); success = true; } catch (FormatException) { } return new Tuple(success, value); } } static class ParsingBinder { static Func parser; public static Func GetParser() { if (parser == null) parser = getParser(); return parser; } static Func getParser() { MethodInfo methodInfo = typeof(T).GetMethod( "Parse", new [] { typeof(String) }); if (methodInfo == null) throw new Exception( "Unable to retrieve a \"Parse\" method for type."); return (Func)Delegate .CreateDelegate(typeof(Func), methodInfo); } } 

这是一种类似的方法,但可以把它想象成一个更好的TryParse方法,它返回一个Tuple (这需要.NET 4)。 元组的第一个属性是指示解析尝试成功或失败的布尔值,第二个属性是类型为generics类型参数的可空值,如果解析失败则为null ,如果解析成功,则为null

它的工作原理是使用reflection从generics类型参数中检索静态Parse(String)方法,并为传入的字符串调用该方法。我将其构建为扩展方法,以允许您执行以下操作:

 var intValue = "1234".TryParse(); var doubleValue = "1234".TryParse(); 

不幸的是,这对enums不起作用,因为它们没有相同的parse方法签名,所以你不能使用这个扩展来解析enum但是要做一个特殊情况就不难破解它了。枚举。

这种方法的一个Parse是,通过reflection检索Parse方法的成本仅在第一次使用时产生,因为为所有后续使用创建了静态委托。


还有一件事 – 这种方法唯一令人笨拙的事情是,没有语言扩展或语法糖可以使这很容易使用。 我希望用这个代码实现的是使用BCL中存在的标准TryParse方法的一种不那么笨重的方式。

我个人认为这种模式很难看:

 Int32 value; if (Int32.TryParse(someString, out value)) // do something with value 

主要是因为它需要提前声明变量并使用out参数。 我上面的方法并没有那么好:

 var result = someString.TryParse(); if (result.Item1) // do something with result.Item2 

真的很酷的是看到一个C#语言扩展,它是为了使用Tuple而构建的,它可以让我们顺利地使用这种类型,但我感觉我写的更多关于它看起来并不可行。

您可以显式使用Nullable关键字,而不是使用问号:例如,

int? 等于Nullable

因此,将原始设计切换为Nullable ParseValue(string valueAsString )应该可以做到这一点:在此之后执行通用实现。

最好实现Extension方法,甚至可以解析Enumerations。 这样你可以得到一个Nullable ,如下所示:

 public static T? Parse(this string text) where T: struct { object o = null; try { var ttype = typeof(T); if (ttype.IsEnum) { T n = default(T); if (Enum.TryParse(text, true, out n)) return n; } else o = Convert.ChangeType(text, ttype); } catch { } if (o == null) return new Nullable(); return new Nullable((T)o); } 

如果您可以等待C#4.0,则可以使用dynamic关键字来解决此类问题。

我真的不明白为什么在安德鲁斯解决方案中使用Tuple,只要我们无论如何返回Nullable,似乎两次做同样的事情。 我编辑了他的解决方案以使用TryParse并允许返回Nullable或指定为参数的默认值。

  ///  /// ///  ///  ///  ///  public static Nullable TryParse(this string aText) where T : struct { T value; if (ParsingBinder.TryParse(aText, out value)) { return value; } return null; } ///  /// ///  ///  ///  ///  ///  public static T TryParse(this string aText, T aDefault) where T : struct { T value; if (!ParsingBinder.TryParse(aText, out value)) { value = aDefault; } return value; } ///  /// ///  ///  static class ParsingBinder where T : struct { ///  /// ///  ///  ///  ///  ///  public delegate bool Delegate_TryParse(string aText, out T aOutput) where T : struct; ///  /// ///  static Delegate_TryParse methodTryParse; ///  /// ///  ///  public static Delegate_TryParse TryParse { get { if (methodTryParse == null) { methodTryParse = GetParserMethod(); } return methodTryParse; } } ///  /// ///  ///  static Delegate_TryParse GetParserMethod() { var typeT = typeof(T); var paramTypes = new Type[] { typeof(string), typeT.MakeByRefType() }; var methodInfo = typeT.GetMethod("TryParse", BindingFlags.Static | BindingFlags.Public, null, paramTypes, null); if (methodInfo == null) { var message = String.Format("Unable to retrieve a 'TryParse' method for type '{0}'.", typeT.Name); throw new Exception(message); } return (Delegate_TryParse) Delegate.CreateDelegate(typeof(Delegate_TryParse), methodInfo); } } 

实际上,您可以更新Matt的代码所做的并制作它,以下是代码:

 enter code here:static T? TryParse(string parse) where T : struct { Type t=typeof(T); if (t==typeof(int)) { int i; if (int.TryParse(parse, out i)) return (T)(object)i; return null; //Console.WriteLine(t.Name); } if (t == typeof(double)) { double i; if (double.TryParse(parse, out i)) return (T)(object)i; return null; } //blabla, more logic like datetime and other data types return null; } 

而且,你可以像这样使用它:双? i = TryParse(“111.111”); 诠释? a = TryParse(“111”);

您列出的这些类型都有一个名为TryParse的静态方法。 它们看起来很相似,但实际上签名完全不同。 它们遵循类似的“模式”,但编译器无法检测到。

您可能会尝试执行以下操作:

  public T? ParseValue(string value) where T : struct { if (typeof(T) == typeof(int)) { int i; if (int.TryParse(value, out i)) return (T)(object)i; return null; } if (typeof(T) == typeof(decimal)) { decimal d; if (decimal.TryParse(value, out d)) return (T)(object)d; return null; } // other supported types... throw new ArgumentException("Type not supported"); } 

但是,你做不到。 编译器无法知道(在编译时)如何将类型’T’转换为int(或任何其他类型)。

您可以使用双重转换来完成此工作。 (谢谢,Dotson)

用法:

  var mydecimal = ParseValue("12.1"); var myint = ParseValue("-22"); var badint = ParseValue("Bad"); // badint.HasValue == false