Using Custom Type Converters

How to create and apply custom type converters for complex command-line argument types

When your command needs to accept complex types that aren't built-in—like points, colors, or domain-specific values—create a custom TypeConverter to parse the string input into your type.

What We're Building

A drawing command accepting Point values in X,Y format. The converter parses --point 10,20 into a strongly-typed struct:

Custom type converters demonstration

Create a Type Converter

Inherit from System.ComponentModel.TypeConverter and override CanConvertFrom and ConvertFrom:

public sealed class PointConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
        => sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
  
    public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
    {
        if (value is string str)
        {
            var parts = str.Split(',');
            if (parts.Length == 2 &&
                int.TryParse(parts[0].Trim(), out var x) &&
                int.TryParse(parts[1].Trim(), out var y))
            {
                return new Point(x, y);
            }
  
            throw new FormatException($"Invalid point format: '{str}'. Expected format: X,Y (e.g., 10,20)");
        }
  
        return base.ConvertFrom(context, culture, value);
    }
}

The Point type it converts to:

public readonly record struct Point(int X, int Y)
{
    public override string ToString() => $"({X}, {Y})";
}

Apply the Converter to an Option

Use the [TypeConverter] attribute on your settings property to specify which converter to use:

public class Settings : CommandSettings
{
    // Custom type with TypeConverter attribute
    [CommandOption("--point <POINT>")]
    [Description("A point in X,Y format (e.g., 10,20)")]
    [TypeConverter(typeof(PointConverter))]
    public required Point Location { get; init; }
  
    // Optional point with nullable
    [CommandOption("--offset [OFFSET]")]
    [Description("Optional offset point")]
    [TypeConverter(typeof(PointConverter))]
    public required FlagValue<Point> Offset { get; init; }
  
    [CommandOption("-c|--color")]
    [Description("The drawing color")]
    [DefaultValue("black")]
    public string Color { get; init; } = "black";
}

Users can now pass points on the command line:

myapp --point 10,20 --color red
myapp --point 100,200 --offset 5,5

Handle Parsing Errors

When the input doesn't match your expected format, throw a FormatException with a helpful message. The framework displays this message to the user:

if (parts.Length != 2 ||
    !int.TryParse(parts[0].Trim(), out var x) ||
    !int.TryParse(parts[1].Trim(), out var y))
{
    throw new FormatException(
        $"Invalid point format: '{str}'. Expected format: X,Y (e.g., 10,20)");
}

When a user provides invalid input like --point abc:

Error: Invalid point format: 'abc'. Expected format: X,Y (e.g., 10,20)

Register Converters on Types You Own

For types you define, you can apply the converter directly to the type instead of each property:

[TypeConverter(typeof(PointConverter))]
public readonly record struct Point(int X, int Y);

Then any property of type Point automatically uses the converter without needing the attribute.

See Also