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 paremeters})
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
,bool
short
,int
,long
,decimal
,double
Guid
,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#
Options for supporting other types
- If you control the type, consider adding a constructor with a single string parameter.
- Create a TypeConverter for your 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)
{
var typeConverter = TypeDescriptor.GetConverter(type);
return typeConverter.CanConvertFrom(typeof(string));
}
public string GetDisplayName(IArgument argument)
{
return 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)!;
}