Skip to content

Commit

Permalink
add missing primitives, support unnamed types
Browse files Browse the repository at this point in the history
  • Loading branch information
lofcz committed Jan 8, 2025
1 parent b8e2090 commit e526f64
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 13 deletions.
124 changes: 124 additions & 0 deletions FastCloner.Tests/SpecialCaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,130 @@ public string Prop
}
}

private unsafe class UnnamedTypeContainer
{
public int Value;
public object? Object;
public delegate*<IServiceProvider, object> Builder;
}

[Test]
public unsafe void Test_Unnamed_Type()
{
// Arrange
int[] array = [1, 2, 3];
IntPtr builder = (IntPtr)GCHandle.Alloc(array, GCHandleType.Pinned);
UnnamedTypeContainer obj = new UnnamedTypeContainer
{
Value = 1,
Object = new object(),
Builder = (delegate*<IServiceProvider, object>)builder
};

// Act
UnnamedTypeContainer result = obj.DeepClone();

// Assert
Assert.Multiple(() =>
{
Assert.That(result, Is.Not.EqualTo(obj));
Assert.That(result.Value, Is.EqualTo(obj.Value));
Assert.That(result.Object, Is.Not.EqualTo(obj.Object));
Assert.That(result.Builder == obj.Builder, Is.True);
});
}

[Test]
public void Test_TimeSpan()
{
// Arrange
TimeSpan obj = TimeSpan.FromHours(42.5);

// Act
TimeSpan result = obj.DeepClone();

// Assert
Assert.That(result, Is.EqualTo(obj));
}

[Test]
public void Test_TimeZoneInfo()
{
// Arrange
TimeZoneInfo obj = TimeZoneInfo.Local;

// Act
TimeZoneInfo result = obj.DeepClone();

// Assert
Assert.That(result, Is.EqualTo(obj));
}

[Test]
public void Test_Half()
{
// Arrange
Half obj = (Half)42.5f;

// Act
Half result = obj.DeepClone();

// Assert
Assert.That(result, Is.EqualTo(obj));
}

[Test]
public void Test_Int128()
{
// Arrange
Int128 obj = Int128.Parse("123456789012345678901234567890");

// Act
Int128 result = obj.DeepClone();

// Assert
Assert.That(result, Is.EqualTo(obj));
}

[Test]
public void Test_UInt128()
{
// Arrange
UInt128 obj = UInt128.Parse("123456789012345678901234567890");

// Act
UInt128 result = obj.DeepClone();

// Assert
Assert.That(result, Is.EqualTo(obj));
}

[Test]
public void Test_Char()
{
// Arrange
char obj = 'Ž';

// Act
char result = obj.DeepClone();

// Assert
Assert.That(result, Is.EqualTo(obj));
}

[Test]
public void Test_Bool()
{
// Arrange
bool obj = true;

// Act
bool result = obj.DeepClone();

// Assert
Assert.That(result, Is.EqualTo(obj));
}

[Test]
public void Test_Notify_Triggered_Correctly()
{
Expand Down
48 changes: 35 additions & 13 deletions FastCloner/Code/FastClonerSafeTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,44 +22,60 @@ internal static class FastClonerSafeTypes
[typeof(double)] = true,
[typeof(decimal)] = true,
[typeof(string)] = true,
[typeof(char)] = true,
[typeof(bool)] = true,
[typeof(sbyte)] = true,
[typeof(nint)] = true,
[typeof(nuint)] = true,
[typeof(Guid)] = true,

// Time-related types
[typeof(TimeSpan)] = true,
[typeof(TimeZoneInfo)] = true,
[typeof(DateTime)] = true,
[typeof(DateTimeOffset)] = true,
[typeof(DateOnly)] = true,
[typeof(TimeOnly)] = true,
[typeof(IntPtr)] = true,
[typeof(UIntPtr)] = true,
[typeof(Guid)] = true,

// Numeric types
[typeof(Half)] = true,
[typeof(Int128)] = true,
[typeof(UInt128)] = true,

// Others
[typeof(DBNull)] = true,
[StringComparer.Ordinal.GetType()] = true,
[StringComparer.OrdinalIgnoreCase.GetType()] = true,
[StringComparer.InvariantCulture.GetType()] = true,
[StringComparer.InvariantCultureIgnoreCase.GetType()] = true
};

static FastClonerSafeTypes()
{
foreach (
Type? x in
new[]
{
Type.GetType("System.RuntimeType"),
Type.GetType("System.RuntimeTypeHandle"),
StringComparer.InvariantCulture.GetType(),
StringComparer.InvariantCultureIgnoreCase.GetType(),
}) KnownTypes.TryAdd(x, true);
List<Type?> safeTypes =
[
Type.GetType("System.RuntimeType"),
Type.GetType("System.RuntimeTypeHandle")
];

foreach (Type? x in safeTypes.OfType<Type>())
{
KnownTypes.TryAdd(x, true);
}
}

private static bool CanReturnSameType(Type type, HashSet<Type>? processingTypes)
{
if (KnownTypes.TryGetValue(type, out bool isSafe))
{
return isSafe;
}

if (typeof(Delegate).IsAssignableFrom(type))
{
KnownTypes.TryAdd(type, false);
return false;
}


// enums are safe
// pointers (e.g. int*) are unsafe, but we cannot do anything with it except blind copy
Expand All @@ -68,6 +84,12 @@ private static bool CanReturnSameType(Type type, HashSet<Type>? processingTypes)
KnownTypes.TryAdd(type, true);
return true;
}

if (type.FullName is null)
{
KnownTypes.TryAdd(type, true);
return true;
}

if (type.FullName.StartsWith("System.Reflection.") && type.Assembly == typeof(PropertyInfo).Assembly)
{
Expand Down

0 comments on commit e526f64

Please sign in to comment.