Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using BenchmarkDotNet.Attributes;

#pragma warning disable CA1822 // Mark members as static

namespace Platform.Converters.Benchmarks
{
public class ConfigurableConverterBenchmarks
{
private static readonly IConfigurableConverter<ulong, int> ThrowExceptionConverter =
ConfigurableConverter<ulong, int>.Create(ConversionConflictResolutionStrategy.ThrowException);
private static readonly IConfigurableConverter<ulong, int> ClampToMaxConverter =
ConfigurableConverter<ulong, int>.Create(ConversionConflictResolutionStrategy.ClampToMax);
private static readonly IConfigurableConverter<ulong, int> AllowOverflowConverter =
ConfigurableConverter<ulong, int>.Create(ConversionConflictResolutionStrategy.AllowOverflow);
private static readonly IConfigurableConverter<ulong, int> ResetToDefaultConverter =
ConfigurableConverter<ulong, int>.Create(ConversionConflictResolutionStrategy.ResetToDefault);

private const ulong InRangeValue = 1000UL;
private const ulong OverflowValue = (ulong)int.MaxValue + 1000UL;

[Benchmark]
public int DirectCast() => (int)InRangeValue;

[Benchmark]
public int UncheckedConverterDefault() => UncheckedConverter<ulong, int>.Default.Convert(InRangeValue);

[Benchmark]
public int CheckedConverterDefault() => CheckedConverter<ulong, int>.Default.Convert(InRangeValue);

[Benchmark]
public int ConfigurableThrowException() => ThrowExceptionConverter.Convert(InRangeValue);

[Benchmark]
public int ConfigurableClampToMax() => ClampToMaxConverter.Convert(InRangeValue);

[Benchmark]
public int ConfigurableAllowOverflow() => AllowOverflowConverter.Convert(InRangeValue);

[Benchmark]
public int ConfigurableResetToDefault() => ResetToDefaultConverter.Convert(InRangeValue);

[Benchmark]
public int SystemConvertToInt32() => Convert.ToInt32(InRangeValue);

[Benchmark]
public int ConfigurableDefault() => ConfigurableConverter<ulong, int>.Default.Convert(InRangeValue);

// Benchmark overflow scenarios (when values are in range, to avoid exceptions in benchmark)
[Benchmark]
public int ConfigurableClampToMaxWithOverflow() => ClampToMaxConverter.Convert(OverflowValue);

[Benchmark]
public int ConfigurableAllowOverflowWithOverflow() => AllowOverflowConverter.Convert(OverflowValue);

[Benchmark]
public int ConfigurableResetToDefaultWithOverflow() => ResetToDefaultConverter.Convert(OverflowValue);
}
}
143 changes: 143 additions & 0 deletions csharp/Platform.Converters.Tests/ConfigurableConverterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System;
using Xunit;

namespace Platform.Converters.Tests
{
public static class ConfigurableConverterTests
{
[Fact]
public static void ThrowExceptionStrategyTest()
{
var converter = ConfigurableConverter<ulong, int>.Create(ConversionConflictResolutionStrategy.ThrowException);

// Within range should work
Assert.Equal(100, converter.Convert(100UL));

// Overflow should throw
Assert.Throws<OverflowException>(() => converter.Convert((ulong)int.MaxValue + 1000));
}

[Fact]
public static void ResetToDefaultStrategyTest()
{
var converter = ConfigurableConverter<ulong, int>.Create(ConversionConflictResolutionStrategy.ResetToDefault);

// Should always return default value
Assert.Equal(0, converter.Convert(100UL));
Assert.Equal(0, converter.Convert((ulong)int.MaxValue + 1000));
}

[Fact]
public static void ClampToMaxStrategyTest()
{
var converter = ConfigurableConverter<ulong, int>.Create(ConversionConflictResolutionStrategy.ClampToMax);

// Within range should work normally
Assert.Equal(100, converter.Convert(100UL));

// Overflow should clamp to max
Assert.Equal(int.MaxValue, converter.Convert((ulong)int.MaxValue + 1000));
}

[Fact]
public static void ClampToMinStrategyTest()
{
var converter = ConfigurableConverter<int, uint>.Create(ConversionConflictResolutionStrategy.ClampToMin);

// Within range should work normally
Assert.Equal(100U, converter.Convert(100));

// Negative should clamp to min (0)
Assert.Equal(0U, converter.Convert(-1000));
}

[Fact]
public static void ClampToNearestStrategyTest()
{
var converter = ConfigurableConverter<long, int>.Create(ConversionConflictResolutionStrategy.ClampToNearest);

// Within range should work normally
Assert.Equal(100, converter.Convert(100L));

// Above max should clamp to max
Assert.Equal(int.MaxValue, converter.Convert(long.MaxValue));

// Below min should clamp to min
Assert.Equal(int.MinValue, converter.Convert(long.MinValue));
}

[Fact]
public static void AllowOverflowStrategyTest()
{
var converter = ConfigurableConverter<ulong, int>.Create(ConversionConflictResolutionStrategy.AllowOverflow);

// Should behave like unchecked conversion
var largeValue = (ulong)int.MaxValue + 1000;
var expected = UncheckedConverter<ulong, int>.Default.Convert(largeValue);
Assert.Equal(expected, converter.Convert(largeValue));
}

[Fact]
public static void SystemConvertStrategyTest()
{
var converter = ConfigurableConverter<ulong, int>.Create(ConversionConflictResolutionStrategy.SystemConvert);

// Within range should work
Assert.Equal(100, converter.Convert(100UL));

// Overflow should throw (like System.Convert)
Assert.Throws<OverflowException>(() => converter.Convert((ulong)int.MaxValue + 1000));
}

[Fact]
public static void UseIConvertibleStrategyTest()
{
var converter = ConfigurableConverter<ulong, int>.Create(ConversionConflictResolutionStrategy.UseIConvertible);

// Within range should work
Assert.Equal(100, converter.Convert(100UL));

// Overflow should throw (like IConvertible)
Assert.Throws<OverflowException>(() => converter.Convert((ulong)int.MaxValue + 1000));
}

[Fact]
public static void WithStrategyTest()
{
var original = ConfigurableConverter<ulong, int>.Create(ConversionConflictResolutionStrategy.ThrowException);
var modified = original.WithStrategy(ConversionConflictResolutionStrategy.ClampToMax);

Assert.Equal(ConversionConflictResolutionStrategy.ThrowException, original.Strategy);
Assert.Equal(ConversionConflictResolutionStrategy.ClampToMax, modified.Strategy);

// Test behavior difference
var testValue = (ulong)int.MaxValue + 1000;
Assert.Throws<OverflowException>(() => original.Convert(testValue));
Assert.Equal(int.MaxValue, modified.Convert(testValue));
}

[Fact]
public static void DefaultConverterTest()
{
var defaultConverter = ConfigurableConverter<ulong, int>.Default;

Assert.Equal(ConversionConflictResolutionStrategy.ThrowException, defaultConverter.Strategy);

// Should behave like ThrowException strategy
Assert.Equal(100, defaultConverter.Convert(100UL));
Assert.Throws<OverflowException>(() => defaultConverter.Convert((ulong)int.MaxValue + 1000));
}

[Fact]
public static void SameTypeConversionTest()
{
var converter = ConfigurableConverter<int, int>.Create(ConversionConflictResolutionStrategy.ClampToMax);

// Same type conversions should always work
Assert.Equal(100, converter.Convert(100));
Assert.Equal(-100, converter.Convert(-100));
Assert.Equal(int.MaxValue, converter.Convert(int.MaxValue));
Assert.Equal(int.MinValue, converter.Convert(int.MinValue));
}
}
}
157 changes: 157 additions & 0 deletions csharp/Platform.Converters/ConfigurableConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using System;
using System.Runtime.CompilerServices;

#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

namespace Platform.Converters
{
/// <summary>
/// <para>
/// Represents the configurable converter with selectable conflict resolution strategies.
/// </para>
/// <para></para>
/// </summary>
public sealed class ConfigurableConverter<TSource, TTarget> : IConfigurableConverter<TSource, TTarget>
{
private readonly ConversionConflictResolutionStrategy _strategy;
private readonly Func<TSource, TTarget> _convertFunction;

/// <summary>
/// <para>
/// Gets the default configurable converter (uses ThrowException strategy).
/// </para>
/// <para></para>
/// </summary>
public static IConfigurableConverter<TSource, TTarget> Default
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
} = Create(ConversionConflictResolutionStrategy.ThrowException);

/// <summary>
/// <para>
/// Gets the strategy used for resolving conversion conflicts.
/// </para>
/// <para></para>
/// </summary>
public ConversionConflictResolutionStrategy Strategy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _strategy;
}

private ConfigurableConverter(ConversionConflictResolutionStrategy strategy, Func<TSource, TTarget> convertFunction)
{
_strategy = strategy;
_convertFunction = convertFunction;
}

/// <summary>
/// <para>
/// Creates a new configurable converter with the specified conflict resolution strategy.
/// </para>
/// <para></para>
/// </summary>
/// <param name="strategy">The conflict resolution strategy to use.</param>
/// <returns>A new configurable converter with the specified strategy.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IConfigurableConverter<TSource, TTarget> Create(ConversionConflictResolutionStrategy strategy)
{
var convertFunction = CreateConvertFunction(strategy);
return new ConfigurableConverter<TSource, TTarget>(strategy, convertFunction);
}

/// <summary>
/// <para>
/// Creates a new converter with the specified conflict resolution strategy.
/// </para>
/// <para></para>
/// </summary>
/// <param name="strategy">The conflict resolution strategy to use.</param>
/// <returns>A new converter with the specified strategy.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IConfigurableConverter<TSource, TTarget> WithStrategy(ConversionConflictResolutionStrategy strategy) => Create(strategy);

/// <summary>
/// <para>Converts the value of the TSource type to the value of the TTarget type.</para>
/// <para>Конвертирует значение типа TSource в значение типа TTarget.</para>
/// </summary>
/// <param name="source">The TSource type value.</param>
/// <returns>The converted value of the TTarget type.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TTarget Convert(TSource source) => _convertFunction(source);

private static Func<TSource, TTarget> CreateConvertFunction(ConversionConflictResolutionStrategy strategy)
{
return strategy switch
{
ConversionConflictResolutionStrategy.ThrowException => CreateCheckedConverter(),
ConversionConflictResolutionStrategy.ResetToDefault => CreateDefaultValueConverter(),
ConversionConflictResolutionStrategy.ClampToMax => CreateClampToMaxConverter(),
ConversionConflictResolutionStrategy.ClampToMin => CreateClampToMinConverter(),
ConversionConflictResolutionStrategy.ClampToNearest => CreateClampToNearestConverter(),
ConversionConflictResolutionStrategy.AllowOverflow => CreateUncheckedConverter(),
ConversionConflictResolutionStrategy.SystemConvert => CreateSystemConvertConverter(),
ConversionConflictResolutionStrategy.UseIConvertible => CreateIConvertibleConverter(),
_ => throw new ArgumentOutOfRangeException(nameof(strategy), strategy, "Unknown conversion conflict resolution strategy.")
};
}

private static Func<TSource, TTarget> CreateCheckedConverter()
{
return source =>
{
try
{
return (TTarget)System.Convert.ChangeType(source, typeof(TTarget))!;
}
catch (InvalidCastException) when (typeof(TTarget).IsValueType)
{
throw new OverflowException($"Value {source} cannot be converted to {typeof(TTarget).Name}.");
}
};
}

private static Func<TSource, TTarget> CreateDefaultValueConverter()
{
return _ => default(TTarget)!;
}

private static Func<TSource, TTarget> CreateClampToMaxConverter()
{
return source => ConversionHelper<TSource, TTarget>.ClampToMax(source);
}

private static Func<TSource, TTarget> CreateClampToMinConverter()
{
return source => ConversionHelper<TSource, TTarget>.ClampToMin(source);
}

private static Func<TSource, TTarget> CreateClampToNearestConverter()
{
return source => ConversionHelper<TSource, TTarget>.ClampToNearest(source);
}

private static Func<TSource, TTarget> CreateUncheckedConverter()
{
return source => UncheckedConverter<TSource, TTarget>.Default.Convert(source);
}

private static Func<TSource, TTarget> CreateSystemConvertConverter()
{
return source => (TTarget)System.Convert.ChangeType(source, typeof(TTarget))!;
}

private static Func<TSource, TTarget> CreateIConvertibleConverter()
{
return source =>
{
if (source is IConvertible convertible)
{
return (TTarget)convertible.ToType(typeof(TTarget), null);
}
throw new InvalidCastException($"Source type {typeof(TSource).Name} does not implement IConvertible.");
};
}
}
}
Loading
Loading