/// <summary> /// Creates a parsing delegate for a specified type. Uses <paramref name="converter"/> if it's compatible; otherwise uses the first compatible match from <see cref="OptionArgumentValueConverters"/>; otherwise attempts to call a <c>TryParse</c> method via reflection. /// If no parser can be found, throws <see cref="InvalidOperationException"/> eagerly (i.e., before parsing user input). /// </summary> /// <param name="type">The type to convert to. May not be <c>null</c>.</param> /// <param name="converter">The custom converter, if any. May be <c>null</c>.</param> private Func <string, object> GetParser(Type type, IOptionArgumentValueConverter converter) { var result = TryGetExactParser(type, converter); if (result != null) { return(result); } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable <>)) { result = TryGetExactParser(type.GenericTypeArguments[0], converter); if (result != null) { return(result); } } throw new InvalidOperationException($"Cannot determine how to parse option argument value of type {type.Name}."); }
/// <summary> /// Creates a parsing delegate for a specified type. Uses <paramref name="converter"/> if it's compatible; otherwise uses the first compatible match from <see cref="OptionArgumentValueConverters"/>; otherwise attempts to call a <c>TryParse</c> method via reflection. /// If no parser can be found, returns <c>null</c>. /// </summary> /// <param name="type">The type to convert to. May not be <c>null</c>.</param> /// <param name="converter">The custom converter, if any. May be <c>null</c>.</param> private Func <string, object> TryGetExactParser(Type type, IOptionArgumentValueConverter converter) { if (converter != null && converter.CanConvert(type)) { return(MakeAction(type, converter)); } var selected = OptionArgumentValueConverters.FirstOrDefault(x => x.CanConvert(type)); if (selected != null) { return(MakeAction(type, selected)); } if (type == typeof(string)) { return(Identity); } var tryParse = type.GetMethod( "TryParse", BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), type.MakeByRefType() }, null); if (tryParse != null && tryParse.ReturnType == typeof(bool)) { // type.TryParse(value, out result) return(value => { var arguments = new object[] { value, null }; var result = (bool)tryParse.Invoke(null, arguments); return result ? arguments[1] : null; }); } if (type.IsEnum) { // Enum.TryParse<type>(value, true, out result) var genericEnumTryParse = typeof(Enum) .GetMethods(BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public) .Where(x => x.Name == "TryParse" && x.IsGenericMethod && x.ContainsGenericParameters) .Select(x => new { Method = x, Parameters = x.GetParameters(), Arguments = x.GetGenericArguments() }) .Where(x => x.Arguments.Length == 1 && x.Parameters.Length == 3 && x.Parameters[0].ParameterType == typeof(string) && x.Parameters[1].ParameterType == typeof(bool) && x.Parameters[2].ParameterType == x.Arguments[0].MakeByRefType()) .Select(x => x.Method) .FirstOrDefault(); if (genericEnumTryParse != null) { var enumTryParse = genericEnumTryParse.MakeGenericMethod(type); return(value => { var arguments = new object[] { value, true, null }; var result = (bool)enumTryParse.Invoke(null, arguments); return result ? arguments[2] : null; }); } } return(null); Func <string, object> MakeAction(Type t, IOptionArgumentValueConverter parser) => value => parser.TryConvert(t, value); }