Skip to content

Commit

Permalink
Merge pull request #38 from zachsaw/stage2
Browse files Browse the repository at this point in the history
Fix nullables and add support for IEnumerable with Add method
  • Loading branch information
zachsaw authored Feb 16, 2021
2 parents b95a62f + bfffb99 commit f2399f0
Show file tree
Hide file tree
Showing 13 changed files with 1,122 additions and 36 deletions.
39 changes: 39 additions & 0 deletions src/Binaron.Serializer.Tests/CustomTestCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Binaron.Serializer.Tests
{
public class CustomTestCollection<T> : IEnumerable<T>
{
private List<T> List = new List<T>();

public T this[int index] => List[index];

public int Count => List.Count;

public void Add(T value) => List.Add(value);


public IEnumerator<T> GetEnumerator() => List.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => List.GetEnumerator();

public class TestCollectionObject
{
public T A { get; set; }

public TestCollectionObject()
{
}

public TestCollectionObject(T a)
{
A = a;
}
}
}
}
112 changes: 111 additions & 1 deletion src/Binaron.Serializer.Tests/ListSerializationTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -265,5 +265,115 @@ private static TList AddItem<TList, TItem>(TList source, TItem item) where TList
throw new NotSupportedException();
}
}

[Test]
public void CustomCollectionIntTest()
{
CustomTestCollection<int> collection = new CustomTestCollection<int>()
{
0, 1, 2, 3, 4
};

var dest = Tester.TestRoundTrip<CustomTestCollection<int>>(collection);
Assert.AreEqual(5, dest.Count);
Assert.AreEqual(0, dest[0]);
Assert.AreEqual(1, dest[1]);
Assert.AreEqual(2, dest[2]);
Assert.AreEqual(3, dest[3]);
Assert.AreEqual(4, dest[4]);
}

[TestCaseSource(typeof(AllTestCases), nameof(AllTestCases.TestCaseOfValues))]
public void CustomCollectionOfTypeTest<TSource>(TSource v)
{
CustomTestCollection<TSource> collection = new CustomTestCollection<TSource>()
{
v
};

var dest = Tester.TestRoundTrip<CustomTestCollection<TSource>>(collection);
Assert.AreEqual(1, dest.Count);
Assert.AreEqual(v, dest[0]);
}


[Test]
public void CustomCollectionObjectTest()
{
CustomTestCollection<CustomTestCollection<int>.TestCollectionObject> collection = new CustomTestCollection<CustomTestCollection<int>.TestCollectionObject>()
{
new CustomTestCollection<int>.TestCollectionObject(0),
new CustomTestCollection<int>.TestCollectionObject(1),
new CustomTestCollection<int>.TestCollectionObject(2),
new CustomTestCollection<int>.TestCollectionObject(3),
new CustomTestCollection<int>.TestCollectionObject(4),
};

var dest = Tester.TestRoundTrip(collection);
Assert.AreEqual(5, dest.Count);
Assert.AreEqual(0, dest[0].A);
Assert.AreEqual(1, dest[1].A);
Assert.AreEqual(2, dest[2].A);
Assert.AreEqual(3, dest[3].A);
Assert.AreEqual(4, dest[4].A);
}

[Test]
public void TestListWithNullInside()
{
CustomTestCollection<CustomTestCollection<int>.TestCollectionObject> collection = new CustomTestCollection<CustomTestCollection<int>.TestCollectionObject>()
{
new CustomTestCollection<int>.TestCollectionObject(0),
new CustomTestCollection<int>.TestCollectionObject(1),
null,
new CustomTestCollection<int>.TestCollectionObject(3),
new CustomTestCollection<int>.TestCollectionObject(4),
};

var dest = Tester.TestRoundTrip(collection);
Assert.AreEqual(5, dest.Count);
Assert.AreEqual(0, dest[0].A);
Assert.AreEqual(1, dest[1].A);
Assert.AreEqual(null, dest[2]);
Assert.AreEqual(3, dest[3].A);
Assert.AreEqual(4, dest[4].A);
}

[Test]
public void TestListOfNullables1()
{
var collection = new CustomTestCollection<int?>() { 1, null, 2 };
using (var ms1 = new MemoryStream())
{
var so = new SerializerOptions();
BinaronConvert.Serialize(collection, ms1, so);
using (var ms2 = new MemoryStream(ms1.ToArray()))
{
var cr2 = BinaronConvert.Deserialize<CustomTestCollection<int?>>(ms2);
Assert.AreEqual(3, cr2.Count);
Assert.AreEqual(1, cr2[0]);
Assert.AreEqual(null, cr2[1]);
Assert.AreEqual(2, cr2[2]);
}
}
}
[Test]
public void TestListOfNullables2()
{
var collection = new List<int?>() { 1, null, 2 };
using (var ms1 = new MemoryStream())
{
var so = new SerializerOptions();
BinaronConvert.Serialize(collection, ms1, so);
using (var ms2 = new MemoryStream(ms1.ToArray()))
{
var cr2 = BinaronConvert.Deserialize<List<int?>>(ms2);
Assert.AreEqual(3, cr2.Count);
Assert.AreEqual(1, cr2[0]);
Assert.AreEqual(null, cr2[1]);
Assert.AreEqual(2, cr2[2]);
}
}
}
}
}
47 changes: 47 additions & 0 deletions src/Binaron.Serializer.Tests/ValueSerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,53 @@ private static object GetEnumNumeric(object source)
}
}

[TestCase(1)]
[TestCase(null)]
public void NullableTest1(int? value)
{
using var stream = new MemoryStream();
BinaronConvert.Serialize(value, stream);
stream.Seek(0, SeekOrigin.Begin);
Assert.AreEqual(value, BinaronConvert.Deserialize<int?>(stream));
}

[TestCase(1, "a")]
[TestCase(null, null)]
[TestCase(1, null)]
[TestCase(null, "abcd")]
public void MemberSetterNullableType1(int? v1, string v2)
{
var tc1 = new TestClass1 { IntValue = v1, StringValue = v2 };
using var stream = new MemoryStream();
BinaronConvert.Serialize(tc1, stream);
stream.Seek(0, SeekOrigin.Begin);
var tc2 = BinaronConvert.Deserialize<TestClass1>(stream);
Assert.AreEqual(v1, tc2.IntValue);
Assert.AreEqual(v2, tc2.StringValue);
}

[TestCase(1, "a")]
[TestCase(null, null)]
[TestCase(1, null)]
[TestCase(null, "abcd")]
public void MemberSetterNullableType2(int? v1, string v2)
{
var tc1 = new List<TestClass1> { new() { IntValue = v1, StringValue = v2 } };
using var stream = new MemoryStream();
BinaronConvert.Serialize(tc1, stream);
stream.Seek(0, SeekOrigin.Begin);
var tc2 = BinaronConvert.Deserialize<List<TestClass1>>(stream);
Assert.AreEqual(1, tc2.Count);
Assert.AreEqual(v1, tc2[0].IntValue);
Assert.AreEqual(v2, tc2[0].StringValue);
}

private class TestClass1
{
public int? IntValue { get; set; }
public string StringValue { get; set; }
}

private class TestClass<T>
{
public DateTime RootValue { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion src/Binaron.Serializer/Accessors/GetterHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal static class GetterHandler
private static readonly ConcurrentDictionary<Type, IMemberGetterHandler<WriterState>[]> MemberGetters = new ConcurrentDictionary<Type, IMemberGetterHandler<WriterState>[]>();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IMemberGetterHandler<WriterState>[] GetGetterHandlers(Type type) => MemberGetters.GetOrAdd(type, _ => CreateGetters(type));
public static IMemberGetterHandler<WriterState>[] GetGetterHandlers(Type type) => MemberGetters.GetOrAdd(type, CreateGetters);

public static class GetterHandlers<T>
{
Expand Down
4 changes: 2 additions & 2 deletions src/Binaron.Serializer/Accessors/SetterHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal static class SetterHandler

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static (Func<object> Activate, IDictionary<string, IMemberSetterHandler<ReaderState>> Setters, Type IDictionaryValueType, Type ActualType) GetActivatorAndSetterHandlers(Type type) =>
ActivatorsAndSetters.GetOrAdd(type, _ => CreateActivatorsAndSetters(type));
ActivatorsAndSetters.GetOrAdd(type, CreateActivatorsAndSetters);

private static (Func<object>, IDictionary<string, IMemberSetterHandler<ReaderState>>, Type IDictionaryValueType, Type ActualType) CreateActivatorsAndSetters(Type type)
{
Expand All @@ -37,7 +37,7 @@ private static (Func<object>, IDictionary<string, IMemberSetterHandler<ReaderSta
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IDictionary<string, IMemberSetterHandler<ReaderState>> GetSetterHandlers(Type type) => SetterHandlers.GetOrAdd(type, _ => CreateSetters(type));
public static IDictionary<string, IMemberSetterHandler<ReaderState>> GetSetterHandlers(Type type) => SetterHandlers.GetOrAdd(type, CreateSetters);

private static Func<object> CreateActivator(Type type) => Activator.Get(type);

Expand Down
4 changes: 2 additions & 2 deletions src/Binaron.Serializer/Creators/Activator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ internal static class Activator
private static readonly ConcurrentDictionary<Type, Func<int, object>> WithCapacityActivators = new ConcurrentDictionary<Type, Func<int, object>>();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Func<object> Get(Type type) => Activators.GetOrAdd(type, _ => CreateActivator(type));
public static Func<object> Get(Type type) => Activators.GetOrAdd(type, CreateActivator);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Func<int, object> GetWithCapacity(Type type) => WithCapacityActivators.GetOrAdd(type, _ => CreateWithCapacityActivator(type));
public static Func<int, object> GetWithCapacity(Type type) => WithCapacityActivators.GetOrAdd(type, CreateWithCapacityActivator);

private static Func<object> CreateActivator(Type type)
{
Expand Down
8 changes: 8 additions & 0 deletions src/Binaron.Serializer/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,18 @@ public static TypeCode GetTypeCode(this Type type)

return type == typeof(Guid) ? TypeCode.Guid : TypeCode.Object;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Type UnwrapNullable(this Type type)
{
var underlyingType = Nullable.GetUnderlyingType(type);
return underlyingType == null ? type : underlyingType;
}
}

internal static class TypeOf<T>
{
public static readonly TypeCode TypeCode = typeof(T).GetTypeCode();
public static readonly TypeCode NullableUnwrappedTypeCode = typeof(T).UnwrapNullable().GetTypeCode();
}
}
48 changes: 48 additions & 0 deletions src/Binaron.Serializer/Infrastructure/EnumerableWrapperWithAdd.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;

namespace Binaron.Serializer.Infrastructure
{
internal static class EnumerableWrapperWithAdd
{
public static readonly ConcurrentDictionary<Type, object> Adders = new ConcurrentDictionary<Type, object>();
}

internal readonly struct EnumerableWrapperWithAdd<T>
{
private readonly Action<object, T> action;
private readonly object result;

public bool HasAddAction => action != null;

public EnumerableWrapperWithAdd(IEnumerable<T> result) : this()
{
action = (Action<object, T>) EnumerableWrapperWithAdd.Adders.GetOrAdd(result.GetType(), CreateAdder);
this.result = result;
}

private static object CreateAdder(Type type)
{
var method = type.GetMethod("Add", new[] {typeof(T)});

if (method == null)
return null;

var dynamicMethod = new DynamicMethod(Guid.NewGuid().ToString(), null, new[] {typeof(object), typeof(T)});

var il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method);
il.Emit(OpCodes.Ret);

return dynamicMethod.CreateDelegate(typeof(Action<object, T>));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(T value) => action(result, value);
}
}
Loading

0 comments on commit f2399f0

Please sign in to comment.