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
)应该可以做到这一点:在此之后执行通用实现。
最好实现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