Data Types#
Arguments can be defined with any type that...
- is a primitive type:
- has a TypeConverter
- contains a string constructor
- has a
public static Parse(string)method orpublic static Parse(string, {optional parameters})
The constructor and static Parse method may contain additional optional parameters but must contain only a single required string parameter.
public class Username
{
public string Value { get; }
public Username(string value) => Value = value;
public Username(string value, DateTime? validUntil = null) => Value = value;
public static Username Parse(string value) => new(value);
public static Username Parse(string value, DateTime? validUntil = null) => new(value, validUntil);
}
Any of those constructors or Parse methods will allow conversion from string input, as shown in this example
public void Login(IConsole console, Username username, Password password)
{
console.WriteLine($"u:{username.Value} p:{password}");
}
$ myapp.exe Login roy rogers
u:roy p:*****
Includes, but not limited to:
string,char,enum,boolshort,int,long,decimal,doubleGuid,Uri,FileInfo,DirectoryInfo
Also supports Nullable<T> and IEnumerable<T> (T[], List<T>, etc.) where T can be converted from string.
Adding support for other types#
Decision guide for supporting custom types:
- You control the type?
→ Add a public constructor with a single string parameter or a public static Parse(string) method
- Need the converter for your business logic too?
→ Create a TypeConverter
- Need to override existing conversion or add CLI-specific behavior?
→ Create a type descriptor
- Only for CLI parsing and you don't control the type?
→ Create a type descriptor
Type Descriptors#
Type descriptors are your best choice when you need
- to override an existing TypeConverter
- conditional logic based on argument metadata (custom attributes, etc)
- the converter only for parsing parameters and not the business logic of your application, ruling out a TypeConvertor
Implement IArgumentTypeDescriptor or instantiate a DelegatedTypeDescriptorAppSettings.ArgumentTypeDescriptors.Register(...).
If the type has a limited range of acceptable values, the descriptor should also implement IAllowedValuesTypeDescriptor. See EnumTypeDescriptor for an example.
public class EnumTypeDescriptor :
IArgumentTypeDescriptor,
IAllowedValuesTypeDescriptor
{
public bool CanSupport(Type type) => type.IsEnum;
public string GetDisplayName(IArgument argument) =>
argument.TypeInfo.UnderlyingType.Name;
public object ParseString(IArgument argument, string value) =>
Enum.Parse(argument.TypeInfo.UnderlyingType, value);
public IEnumerable<string> GetAllowedValues(IArgument argument) =>
Enum.GetNames(argument.TypeInfo.UnderlyingType);
}
Use DelegatedTypeDescriptor just to override the display text or factory method for the type.
new DelegatedTypeDescriptor<string>(Resources.A.Type_Text, v => v),
See StringCtorTypeDescriptor and ComponentModelTypeDescriptor for examples to create your own.
public class ComponentModelTypeDescriptor : IArgumentTypeDescriptor
{
public bool CanSupport(Type type) =>
TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(string));
public string GetDisplayName(IArgument argument) =>
argument.TypeInfo.UnderlyingType.Name;
public object ParseString(IArgument argument, string value)
{
var typeConverter = argument.Arity.AllowsMany()
? TypeDescriptor.GetConverter(argument.TypeInfo.UnderlyingType)
: TypeDescriptor.GetConverter(argument.TypeInfo.Type);
return typeConverter.ConvertFrom(value)!;
}