ValueConverter Type Conversion Utility
ValueConverter provides a unified type conversion interface. It uses a registry pattern to manage converters and supports both built-in and custom converters.
Namespace
Section titled “Namespace”using MiCake.Util.Convert;Basic Usage
Section titled “Basic Usage”The Convert Method
Section titled “The Convert Method”// String to integerint intValue = ValueConverter.Convert<string, int>("123");
// String to dateDateTime dateValue = ValueConverter.Convert<string, DateTime>("2024-01-01");
// Guid conversionGuid guid = ValueConverter.Convert<string, Guid>("550e8400-e29b-41d4-a716-446655440000");
// Version conversionVersion? version = ValueConverter.Convert<string, Version>("1.2.3");Convert Method Behavior
Section titled “Convert Method Behavior”- null value handling: throws an
ArgumentNullExceptionwhen the source value is null - Conversion failure: returns the default value of the target type (such as
0,null,Guid.Empty) without throwing an exception - Conversion order:
- Queries the registry for custom converters (in registration order)
- Uses the built-in
SystemValueConverteras a fallback
Custom Converters
Section titled “Custom Converters”Creating a Converter
Section titled “Creating a Converter”public class MoneyConverter : IValueConverter<string, Money>{ public bool CanConvert(string value) { return !string.IsNullOrEmpty(value) && value.Contains(' '); }
public Money? Convert(string value) { var parts = value.Split(' '); if (parts.Length != 2) return null;
if (!decimal.TryParse(parts[0], out var amount)) return null;
return new Money(amount, parts[1]); }}Registering a Converter
Section titled “Registering a Converter”// Register using a factory methodValueConverter.RegisterConverter(() => new MoneyConverter());
// Register using an instanceValueConverter.RegisterConverter(new MoneyConverter());
// Generic registrationValueConverter.RegisterConverter<string, Money>(() => new MoneyConverter());Using a Custom Converter
Section titled “Using a Custom Converter”// Use it directly after registrationvar money = ValueConverter.Convert<string, Money>("99.99 USD");Console.WriteLine($"{money.Amount} {money.Currency}"); // 99.99 USDRegistry Management
Section titled “Registry Management”HasConverter - Check for a Converter
Section titled “HasConverter - Check for a Converter”if (ValueConverter.HasConverter<string, Money>()){ Console.WriteLine("Converter is registered");}ClearConverters - Clear Specified Converters
Section titled “ClearConverters - Clear Specified Converters”// Clear all converters from string to MoneyValueConverter.ClearConverters<string, Money>();ClearAll - Clear All Converters
Section titled “ClearAll - Clear All Converters”// Clear all custom converters (built-in converters are also cleared)ValueConverter.ClearAll();SetRegistry - Set a Custom Registry
Section titled “SetRegistry - Set a Custom Registry”// Create a custom registryvar customRegistry = new DefaultConverterRegistry();customRegistry.Register<string, MyType>(() => new MyTypeConverter());
// Apply the custom registryValueConverter.SetRegistry(customRegistry);ResetRegistry - Reset to the Default Registry
Section titled “ResetRegistry - Reset to the Default Registry”// Reset to the default registry and re-register the built-in convertersValueConverter.ResetRegistry();Built-in Converters
Section titled “Built-in Converters”MiCake pre-registers the following built-in converters:
GuidValueConverter
Section titled “GuidValueConverter”// String to GuidGuid guid1 = ValueConverter.Convert<string, Guid>("550e8400-e29b-41d4-a716-446655440000");
// Guid to Guid (returns directly)Guid guid2 = ValueConverter.Convert<Guid, Guid>(guid1);
// Returns Guid.Empty on failureGuid empty = ValueConverter.Convert<string, Guid>("invalid"); // Guid.EmptyVersionValueConverter
Section titled “VersionValueConverter”// String to VersionVersion? version1 = ValueConverter.Convert<string, Version>("1.2.3");Console.WriteLine(version1); // 1.2.3
// Version to Version (returns directly)Version? version2 = ValueConverter.Convert<Version, Version>(version1);
// Returns null on failureVersion? nullVersion = ValueConverter.Convert<string, Version>("invalid"); // nullSystemValueConverter
Section titled “SystemValueConverter”Acts as a fallback converter, using System.Convert.ChangeType:
// Supports most basic type conversionsint intVal = ValueConverter.Convert<string, int>("42");double doubleVal = ValueConverter.Convert<string, double>("3.14");bool boolVal = ValueConverter.Convert<string, bool>("true");DateTime dateVal = ValueConverter.Convert<string, DateTime>("2024-01-01");Registry and Thread Safety
Section titled “Registry and Thread Safety”DefaultConverterRegistry uses locks to protect read/write operations, ensuring thread safety:
// Safe registration in multi-threaded environmentsParallel.For(0, 100, i =>{ ValueConverter.RegisterConverter<string, int>(() => new MyIntConverter());});Usage Examples
Section titled “Usage Examples”Complex Type Conversion
Section titled “Complex Type Conversion”public class ProductDto{ public string Id { get; set; } public string Price { get; set; } public string Category { get; set; }}
// Register convertersValueConverter.RegisterConverter<string, int>(() => new StringToIntConverter());ValueConverter.RegisterConverter<string, decimal>(() => new StringToDecimalConverter());
// Usagevar dto = new ProductDto{ Id = "123", Price = "99.99", Category = "1"};
int id = ValueConverter.Convert<string, int>(dto.Id);decimal price = ValueConverter.Convert<string, decimal>(dto.Price);int categoryId = ValueConverter.Convert<string, int>(dto.Category);Using in a Service
Section titled “Using in a Service”public class DataImportService : IScopedService{ public Product ParseProduct(Dictionary<string, string> data) { return new Product { Id = ValueConverter.Convert<string, int>(data["Id"]), Name = data["Name"], Price = ValueConverter.Convert<string, decimal>(data["Price"]), CreatedAt = ValueConverter.Convert<string, DateTime>(data["CreatedAt"]) }; }}Chained Conversion
Section titled “Chained Conversion”// Register multiple converters to implement chained conversionValueConverter.RegisterConverter<string, Guid>(() => new StringToGuidConverter());ValueConverter.RegisterConverter<Guid, int>(() => new GuidToIntConverter());
// Step-by-step conversionstring guidString = "550e8400-e29b-41d4-a716-446655440000";Guid guid = ValueConverter.Convert<string, Guid>(guidString);int hashCode = ValueConverter.Convert<Guid, int>(guid);Best Practices
Section titled “Best Practices”1. Register Specific Converters
Section titled “1. Register Specific Converters”// ✅ Correct: register a dedicated converter to override the default behaviorValueConverter.RegisterConverter<string, Money>(() => new MoneyConverter());
// ❌ Not recommended: rely on SystemValueConverter for complex typesvar money = ValueConverter.Convert<string, Money>("99.99 USD"); // may fail2. Handle Conversion Failures
Section titled “2. Handle Conversion Failures”// ✅ Correct: check the return valueint? value = ValueConverter.Convert<string, int?>("invalid");if (value == null){ Console.WriteLine("Conversion failed");}
// ⚠️ Note: non-nullable types return the default valueint defaultValue = ValueConverter.Convert<string, int>("invalid"); // returns 03. Thread-Safe Registration
Section titled “3. Thread-Safe Registration”// ✅ Correct: register at application startuppublic class MyModule : MiCakeModule{ public override void ConfigureServices(ModuleConfigServiceContext context) { ValueConverter.RegisterConverter<string, Money>(() => new MoneyConverter()); base.ConfigureServices(context); }}
// ❌ Not recommended: frequently register/clear at runtimeValueConverter.RegisterConverter<string, int>(() => new MyConverter());ValueConverter.ClearConverters<string, int>();4. Converter Order
Section titled “4. Converter Order”// ✅ Correct: register by priorityValueConverter.RegisterConverter<string, int>(() => new SpecialIntConverter()); // priorityValueConverter.RegisterConverter<string, int>(() => new GeneralIntConverter()); // fallback
// Convert tries them in registration orderint value = ValueConverter.Convert<string, int>("123"); // uses SpecialIntConverter5. Implement Idempotent Converters
Section titled “5. Implement Idempotent Converters”// ✅ Correct: an idempotent converterpublic class SafeIntConverter : IValueConverter<string, int>{ public bool CanConvert(string value) { return int.TryParse(value, out _); }
public int? Convert(string value) { return int.TryParse(value, out var result) ? result : null; }}
// ❌ Wrong: a non-idempotent converterpublic class CountingConverter : IValueConverter<string, int>{ private int _count = 0;
public int? Convert(string value) { return _count++; // returns a different result on each call }}Management Method Summary
Section titled “Management Method Summary”| Method | Description |
|---|---|
Convert<TSource, TDest>(source) |
Converts a value, returns the default value on failure |
RegisterConverter<TSource, TDest>(factory) |
Registers a converter factory |
RegisterConverter(converter) |
Registers a converter instance |
HasConverter<TSource, TDest>() |
Checks whether a converter exists |
ClearConverters<TSource, TDest>() |
Clears converters for the specified types |
ClearAll() |
Clears all converters |
SetRegistry(registry) |
Sets a custom registry |
ResetRegistry() |
Resets to the default registry |
Important Notes
Section titled “Important Notes”- Conversion failure returns the default value: the
Convertmethod does not throw exceptions - null value throws an exception: throws an
ArgumentNullExceptionwhen the source value is null - Converter order: converters are tried in registration order
- Thread safety:
DefaultConverterRegistrysupports thread-safe operations - Built-in fallback:
SystemValueConverteracts as the last-resort fallback converter
