Custom conversions

edit

Derive from DefaultConverter

DefaultConverter is not sealed. Its conversions are protected virtual methods, so you can change one and keep the others:

private sealed class YesNoConverter : DefaultConverter
{
    protected override bool TryConvert(object? input, Type conversionType, IFormatProvider? provider, out object? value)
    {
        if (input is bool boolean && conversionType == typeof(string))
        {
            value = boolean ? "Yes" : "No";
            return true;
        }

        return base.TryConvert(input, conversionType, provider, out value);
    }
}
var converter = new YesNoConverter();

Assert.True(converter.TryChangeType(true, CultureInfo.InvariantCulture, out string? text));
Assert.Equal("Yes", text);

// The other conversions are unchanged
Assert.Equal(42, converter.ChangeType("42", defaultValue: 0, CultureInfo.InvariantCulture));
  • TryConvert(object? input, Type conversionType, IFormatProvider? provider, out object? value) runs every conversion. Override it to handle a pair of types before the default rules.
  • The overloads of TryConvert with a typed out parameter (out int, out Guid, out DateTime, …) convert to one type. Override one to change how that type is read.
  • TryConvertEnum converts to enums, and TryConvertUsingImplicitConverter applies implicit operators.
  • TryChangeType, the public entry point, is virtual too.

The extension methods of ConvertUtilities, such as TryChangeType<T> and ChangeType<T>, work with any IConverter, so your converter has the same API as ConvertUtilities.

Implement IConverter

IConverter has a single method, TryChangeType(input, conversionType, provider, out value). Implement it to write a converter from scratch, for instance one that delegates to a serializer, and use it with the same extension methods.