Skip to content

Commit 350a618

Browse files
authored
all default value factories should be generic (dotnet#1968)
1 parent 6e931b6 commit 350a618

File tree

8 files changed

+136
-115
lines changed

8 files changed

+136
-115
lines changed

src/Common/ArgumentBuilder.cs

+15-3
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,25 @@ public static Argument CreateArgument(Type valueType, string name = "value")
1717
var argumentType = typeof(Argument<>).MakeGenericType(valueType);
1818

1919
#if NET6_0_OR_GREATER
20-
var ctor = (ConstructorInfo)argumentType.GetMemberWithSameMetadataDefinitionAs(_ctor);
20+
var ctor = (ConstructorInfo)argumentType.GetMemberWithSameMetadataDefinitionAs(_ctor);
2121
#else
2222
var ctor = argumentType.GetConstructor(new[] { typeof(string), typeof(string) });
2323
#endif
2424

25-
var option = (Argument)ctor.Invoke(new object[] { name, null });
25+
return (Argument)ctor.Invoke(new object[] { name, null });
26+
}
27+
28+
internal static Argument CreateArgument(ParameterInfo argsParam)
29+
{
30+
if (!argsParam.HasDefaultValue)
31+
{
32+
return CreateArgument(argsParam.ParameterType, argsParam.Name);
33+
}
34+
35+
var argumentType = typeof(Argument<>).MakeGenericType(argsParam.ParameterType);
36+
37+
var ctor = argumentType.GetConstructor(new[] { typeof(string), argsParam.ParameterType, typeof(string) });
2638

27-
return option;
39+
return (Argument)ctor.Invoke(new object[] { argsParam.Name, argsParam.DefaultValue, null });
2840
}
2941
}

src/Common/OptionBuilder.cs

+28-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ static OptionBuilder()
1414
_ctor = typeof(Option<string>).GetConstructor(new[] { typeof(string), typeof(string) });
1515
}
1616

17-
public static Option CreateOption(string name, Type valueType)
17+
public static Option CreateOption(string name, Type valueType, string description = null)
1818
{
1919
var optionType = typeof(Option<>).MakeGenericType(valueType);
2020

@@ -24,8 +24,34 @@ public static Option CreateOption(string name, Type valueType)
2424
var ctor = optionType.GetConstructor(new[] { typeof(string), typeof(string) });
2525
#endif
2626

27-
var option = (Option)ctor.Invoke(new object[] { name, null });
27+
var option = (Option)ctor.Invoke(new object[] { name, description });
2828

2929
return option;
3030
}
31+
32+
public static Option CreateOption(string name, Type valueType, string description, Func<object> defaultValueFactory)
33+
{
34+
if (defaultValueFactory == null)
35+
{
36+
return CreateOption(name, valueType, description);
37+
}
38+
39+
var optionType = typeof(Bridge<>).MakeGenericType(valueType);
40+
41+
var ctor = optionType.GetConstructor(new[] { typeof(string), typeof(Func<object>), typeof(string) });
42+
43+
var option = (Option)ctor.Invoke(new object[] { name, defaultValueFactory, description });
44+
45+
return option;
46+
}
47+
48+
private class Bridge<T> : Option<T>
49+
{
50+
public Bridge(string name, Func<object> defaultValueFactory, string description)
51+
: base(name,
52+
() => (T)defaultValueFactory(), // this type exists only for an easy Func<object> => Func<T> transformation
53+
description)
54+
{
55+
}
56+
}
3157
}

src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt

+7-5
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,16 @@ System.CommandLine
99
public System.Object GetDefaultValue()
1010
public ParseResult Parse(System.String commandLine)
1111
public ParseResult Parse(System.String[] args)
12-
public System.Void SetDefaultValue(System.Object value)
13-
public System.Void SetDefaultValueFactory(System.Func<System.Object> defaultValueFactory)
14-
public System.Void SetDefaultValueFactory(System.Func<System.CommandLine.Parsing.ArgumentResult,System.Object> defaultValueFactory)
1512
public System.String ToString()
1613
public class Argument<T> : Argument, IValueDescriptor<T>, System.CommandLine.Binding.IValueDescriptor
1714
.ctor()
1815
.ctor(System.String name, System.String description = null)
1916
.ctor(System.String name, Func<T> defaultValueFactory, System.String description = null)
17+
.ctor(System.String name, T defaultValue, System.String description = null)
2018
.ctor(Func<T> defaultValueFactory)
2119
.ctor(System.String name, Func<System.CommandLine.Parsing.ArgumentResult,T> parse, System.Boolean isDefault = False, System.String description = null)
2220
.ctor(Func<System.CommandLine.Parsing.ArgumentResult,T> parse, System.Boolean isDefault = False)
21+
public System.Boolean HasDefaultValue { get; }
2322
public System.Type ValueType { get; }
2423
public Argument<T> AcceptLegalFileNamesOnly()
2524
public Argument<T> AcceptLegalFilePathsOnly()
@@ -28,6 +27,9 @@ System.CommandLine
2827
public Argument<T> AddCompletions(System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.String>> completionsDelegate)
2928
public Argument<T> AddCompletions(System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem>> completionsDelegate)
3029
public Argument<T> AddValidator(System.Action<System.CommandLine.Parsing.ArgumentResult> validate)
30+
public System.Void SetDefaultValue(T value)
31+
public System.Void SetDefaultValueFactory(Func<T> defaultValueFactory)
32+
public System.Void SetDefaultValueFactory(Func<System.CommandLine.Parsing.ArgumentResult,T> defaultValueFactory)
3133
public struct ArgumentArity : System.ValueType, System.IEquatable<ArgumentArity>
3234
public static ArgumentArity ExactlyOne { get; }
3335
public static ArgumentArity OneOrMore { get; }
@@ -197,8 +199,6 @@ System.CommandLine
197199
public System.Boolean HasAliasIgnoringPrefix(System.String alias)
198200
public ParseResult Parse(System.String commandLine)
199201
public ParseResult Parse(System.String[] args)
200-
public System.Void SetDefaultValue(System.Object value)
201-
public System.Void SetDefaultValueFactory(System.Func<System.Object> defaultValueFactory)
202202
public class Option<T> : Option, IValueDescriptor<T>, System.CommandLine.Binding.IValueDescriptor
203203
.ctor(System.String name, System.String description = null)
204204
.ctor(System.String[] aliases, System.String description = null)
@@ -213,6 +213,8 @@ System.CommandLine
213213
public Option<T> AddCompletions(System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.String>> completionsDelegate)
214214
public Option<T> AddCompletions(System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem>> completionsDelegate)
215215
public Option<T> AddValidator(System.Action<System.CommandLine.Parsing.OptionResult> validate)
216+
public System.Void SetDefaultValue(T value)
217+
public System.Void SetDefaultValueFactory(Func<T> defaultValueFactory)
216218
public static class OptionValidation
217219
public static Option<System.IO.FileInfo> AcceptExistingOnly(this Option<System.IO.FileInfo> option)
218220
public static Option<System.IO.DirectoryInfo> AcceptExistingOnly(this Option<System.IO.DirectoryInfo> option)

src/System.CommandLine.DragonFruit/CommandLine.cs

+6-33
Original file line numberDiff line numberDiff line change
@@ -165,21 +165,7 @@ public static void ConfigureFromMethod(
165165

166166
if (method.GetParameters().FirstOrDefault(p => _argumentParameterNames.Contains(p.Name)) is { } argsParam)
167167
{
168-
var argument = ArgumentBuilder.CreateArgument(argsParam.ParameterType, argsParam.Name);
169-
170-
if (argsParam.HasDefaultValue)
171-
{
172-
if (argsParam.DefaultValue is not null)
173-
{
174-
argument.SetDefaultValue(argsParam.DefaultValue);
175-
}
176-
else
177-
{
178-
argument.SetDefaultValueFactory(() => null);
179-
}
180-
}
181-
182-
command.AddArgument(argument);
168+
command.AddArgument(ArgumentBuilder.CreateArgument(argsParam));
183169
}
184170

185171
command.Handler = CommandHandler.Create(method, target);
@@ -285,24 +271,11 @@ public static IEnumerable<Option> BuildOptions(this MethodInfo method)
285271
}
286272

287273
public static Option BuildOption(this ParameterDescriptor parameter)
288-
{
289-
Func<object> getDefaultValue = null;
290-
if (parameter.HasDefaultValue)
291-
{
292-
getDefaultValue = parameter.GetDefaultValue;
293-
}
294-
295-
var option = OptionBuilder.CreateOption(parameter.BuildAlias(), parameter.ValueType);
296-
297-
option.Description = parameter.ValueName;
298-
299-
if (getDefaultValue is not null)
300-
{
301-
option.SetDefaultValueFactory(getDefaultValue);
302-
}
303-
304-
return option;
305-
}
274+
=> OptionBuilder.CreateOption(
275+
parameter.BuildAlias(),
276+
parameter.ValueType,
277+
parameter.ValueName,
278+
parameter.HasDefaultValue ? parameter.GetDefaultValue : null);
306279

307280
private static string GetDefaultXmlDocsFileLocation(Assembly assembly)
308281
{

src/System.CommandLine/Argument.cs

+2-45
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ namespace System.CommandLine
1414
/// </summary>
1515
public abstract class Argument : Symbol, IValueDescriptor
1616
{
17-
private Func<ArgumentResult, object?>? _defaultValueFactory;
1817
private ArgumentArity _arity;
1918
private TryConvertArgument? _convertArguments;
2019
private List<Func<CompletionContext, IEnumerable<CompletionItem>>>? _completions = null;
@@ -115,54 +114,12 @@ private protected override string DefaultName
115114
return GetDefaultValue(new ArgumentResult(this, null));
116115
}
117116

118-
internal object? GetDefaultValue(ArgumentResult argumentResult)
119-
{
120-
if (_defaultValueFactory is null)
121-
{
122-
throw new InvalidOperationException($"Argument \"{Name}\" does not have a default value");
123-
}
124-
125-
return _defaultValueFactory.Invoke(argumentResult);
126-
}
127-
128-
/// <summary>
129-
/// Sets the default value for the argument.
130-
/// </summary>
131-
/// <param name="value">The default value for the argument.</param>
132-
public void SetDefaultValue(object? value)
133-
{
134-
SetDefaultValueFactory(() => value);
135-
}
136-
137-
/// <summary>
138-
/// Sets a delegate to invoke when the default value for the argument is required.
139-
/// </summary>
140-
/// <param name="defaultValueFactory">The delegate to invoke to return the default value.</param>
141-
/// <exception cref="ArgumentNullException">Thrown when <paramref name="defaultValueFactory"/> is null.</exception>
142-
public void SetDefaultValueFactory(Func<object?> defaultValueFactory)
143-
{
144-
if (defaultValueFactory is null)
145-
{
146-
throw new ArgumentNullException(nameof(defaultValueFactory));
147-
}
148-
149-
SetDefaultValueFactory(_ => defaultValueFactory());
150-
}
151-
152-
/// <summary>
153-
/// Sets a delegate to invoke when the default value for the argument is required.
154-
/// </summary>
155-
/// <param name="defaultValueFactory">The delegate to invoke to return the default value.</param>
156-
/// <remarks>In this overload, the <see cref="ArgumentResult"/> is provided to the delegate.</remarks>
157-
public void SetDefaultValueFactory(Func<ArgumentResult, object?> defaultValueFactory)
158-
{
159-
_defaultValueFactory = defaultValueFactory ?? throw new ArgumentNullException(nameof(defaultValueFactory));
160-
}
117+
internal abstract object? GetDefaultValue(ArgumentResult argumentResult);
161118

162119
/// <summary>
163120
/// Specifies if a default value is defined for the argument.
164121
/// </summary>
165-
public bool HasDefaultValue => _defaultValueFactory is not null;
122+
public abstract bool HasDefaultValue { get; }
166123

167124
internal virtual bool HasCustomParser => false;
168125

src/System.CommandLine/Argument{T}.cs

+64-12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace System.CommandLine
1212
/// <inheritdoc cref="Argument" />
1313
public class Argument<T> : Argument, IValueDescriptor<T>
1414
{
15+
private Func<ArgumentResult, T>? _defaultValueFactory;
1516
private readonly bool _hasCustomParser;
1617

1718
/// <summary>
@@ -40,26 +41,30 @@ public Argument(
4041
Func<T> defaultValueFactory,
4142
string? description = null) : this(name, description)
4243
{
43-
if (defaultValueFactory is null)
44-
{
45-
throw new ArgumentNullException(nameof(defaultValueFactory));
46-
}
47-
4844
SetDefaultValueFactory(() => defaultValueFactory());
4945
}
5046

47+
/// <summary>
48+
/// Initializes a new instance of the Argument class.
49+
/// </summary>
50+
/// <param name="name">The name of the argument.</param>
51+
/// <param name="defaultValue">The default value.</param>
52+
/// <param name="description">The description of the argument, shown in help.</param>
53+
public Argument(
54+
string name,
55+
T defaultValue,
56+
string? description = null) : this(name, description)
57+
{
58+
SetDefaultValue(defaultValue);
59+
}
60+
5161
/// <summary>
5262
/// Initializes a new instance of the Argument class.
5363
/// </summary>
5464
/// <param name="defaultValueFactory">The delegate to invoke to return the default value.</param>
5565
/// <exception cref="ArgumentNullException">Thrown when <paramref name="defaultValueFactory"/> is null.</exception>
5666
public Argument(Func<T> defaultValueFactory) : this()
5767
{
58-
if (defaultValueFactory is null)
59-
{
60-
throw new ArgumentNullException(nameof(defaultValueFactory));
61-
}
62-
6368
SetDefaultValueFactory(() => defaultValueFactory());
6469
}
6570

@@ -73,7 +78,7 @@ public Argument(Func<T> defaultValueFactory) : this()
7378
/// <exception cref="ArgumentNullException">Thrown when <paramref name="parse"/> is null.</exception>
7479
public Argument(
7580
string? name,
76-
Func<ArgumentResult, T> parse,
81+
Func<ArgumentResult, T> parse,
7782
bool isDefault = false,
7883
string? description = null) : this(name, description)
7984
{
@@ -84,7 +89,7 @@ public Argument(
8489

8590
if (isDefault)
8691
{
87-
SetDefaultValueFactory(argumentResult => parse(argumentResult));
92+
SetDefaultValueFactory(parse);
8893
}
8994

9095
ConvertArguments = (ArgumentResult argumentResult, out object? value) =>
@@ -120,6 +125,53 @@ public Argument(Func<ArgumentResult, T> parse, bool isDefault = false) : this(nu
120125
/// <inheritdoc />
121126
public override Type ValueType => typeof(T);
122127

128+
/// <inheritdoc />
129+
public override bool HasDefaultValue => _defaultValueFactory is not null;
130+
131+
/// <summary>
132+
/// Sets the default value for the argument.
133+
/// </summary>
134+
/// <param name="value">The default value for the argument.</param>
135+
public void SetDefaultValue(T value)
136+
{
137+
SetDefaultValueFactory(() => value);
138+
}
139+
140+
/// <summary>
141+
/// Sets a delegate to invoke when the default value for the argument is required.
142+
/// </summary>
143+
/// <param name="defaultValueFactory">The delegate to invoke to return the default value.</param>
144+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="defaultValueFactory"/> is null.</exception>
145+
public void SetDefaultValueFactory(Func<T> defaultValueFactory)
146+
{
147+
if (defaultValueFactory is null)
148+
{
149+
throw new ArgumentNullException(nameof(defaultValueFactory));
150+
}
151+
152+
SetDefaultValueFactory(_ => defaultValueFactory());
153+
}
154+
155+
/// <summary>
156+
/// Sets a delegate to invoke when the default value for the argument is required.
157+
/// </summary>
158+
/// <param name="defaultValueFactory">The delegate to invoke to return the default value.</param>
159+
/// <remarks>In this overload, the <see cref="ArgumentResult"/> is provided to the delegate.</remarks>
160+
public void SetDefaultValueFactory(Func<ArgumentResult, T> defaultValueFactory)
161+
{
162+
_defaultValueFactory = defaultValueFactory ?? throw new ArgumentNullException(nameof(defaultValueFactory));
163+
}
164+
165+
internal override object? GetDefaultValue(ArgumentResult argumentResult)
166+
{
167+
if (_defaultValueFactory is null)
168+
{
169+
throw new InvalidOperationException($"Argument \"{Name}\" does not have a default value");
170+
}
171+
172+
return _defaultValueFactory.Invoke(argumentResult);
173+
}
174+
123175
/// <summary>
124176
/// Adds completions for the argument.
125177
/// </summary>

src/System.CommandLine/Option.cs

-15
Original file line numberDiff line numberDiff line change
@@ -121,21 +121,6 @@ public bool HasAliasIgnoringPrefix(string alias)
121121
return false;
122122
}
123123

124-
/// <summary>
125-
/// Sets the default value for the option.
126-
/// </summary>
127-
/// <param name="value">The default value for the option.</param>
128-
public void SetDefaultValue(object? value) =>
129-
Argument.SetDefaultValue(value);
130-
131-
/// <summary>
132-
/// Sets a delegate to invoke when the default value for the option is required.
133-
/// </summary>
134-
/// <param name="defaultValueFactory">The delegate to invoke to return the default value.</param>
135-
/// <exception cref="ArgumentNullException">Thrown when <paramref name="defaultValueFactory"/> is null.</exception>
136-
public void SetDefaultValueFactory(Func<object?> defaultValueFactory) =>
137-
Argument.SetDefaultValueFactory(defaultValueFactory);
138-
139124
/// <summary>
140125
/// Gets a value that indicates whether multiple argument tokens are allowed for each option identifier token.
141126
/// </summary>

0 commit comments

Comments
 (0)