Skip to content

Commit

Permalink
completed work on expression tree serialization; many bugfixes; bette…
Browse files Browse the repository at this point in the history
…r type-config api
  • Loading branch information
rikimaru0345 committed Feb 10, 2019
1 parent fb60053 commit 5c10f30
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 55 deletions.
129 changes: 93 additions & 36 deletions samples/LiveTesting/Program.cs
Expand Up @@ -101,7 +101,22 @@ static void Main(string[] args)
}


class ReadonlyTestClass
class ReadonlyTestBaseClass
{
readonly string _baseName = "base default";

public ReadonlyTestBaseClass()
{
_baseName = "base ctor";
}

protected string ProtectedGetBaseName()
{
return _baseName;
}
}

class ReadonlyTestClass : ReadonlyTestBaseClass
{
readonly string _name = "default";

Expand All @@ -114,66 +129,108 @@ public string GetName()
{
return _name;
}

public string GetBaseName()
{
return base.ProtectedGetBaseName();
}
}


static void ExpressionTreesTest()
{
Expression<Func<string, int, char>> getCharAtIndex = (text, index) => (text.ElementAt(index).ToString() + text[index])[0];
// Primitive test
{
SerializerConfig config = new SerializerConfig();
config.OnConfigNewType = t =>
{
if (t.Type == typeof(ReadonlyTestClass))
{
t.ConstructByUninitialized();
t.SetReadonlyHandling(ReadonlyFieldHandling.ForcedOverwrite);
t.SetTargetMembers(TargetMember.PrivateFields);
}
};

var del = getCharAtIndex.Compile();
var ceras = new CerasSerializer(config);

string inputString = "abcde";
char c1 = del(inputString, 2);
var obj = new ReadonlyTestClass("a");
var data = ceras.Serialize(obj);

var clone = ceras.Deserialize<ReadonlyTestClass>(data);

// Serialize and deserialize delegate
SerializerConfig config = new SerializerConfig();
Console.WriteLine();
}

// Simple test
{
Expression<Action> methodHelperExp = () => StaticPoolTest.CreatePerson();
var methodInfo = ((MethodCallExpression)methodHelperExp.Body).Method;

MethodCallExpression methodCallExp = Expression.Call(methodInfo);

ExpressionFormatterResolver.Configure(config);

// Serialize and deserialize delegate
SerializerConfig config = new SerializerConfig();
ExpressionFormatterResolver.Configure(config);
var ceras = new CerasSerializer(config);

var ceras = new CerasSerializer(config);

var data = ceras.Serialize<object>(getCharAtIndex);
var dataAsStr = Encoding.ASCII.GetString(data).Replace('\0', ' ');
var data = ceras.Serialize<object>(methodCallExp);
var dataAsStr = Encoding.ASCII.GetString(data).Replace('\0', ' ');

var clonedExp = (MethodCallExpression)ceras.Deserialize<object>(data);

var clonedExp = (Expression<Func<string, int, char>>)ceras.Deserialize<object>(data);
Console.WriteLine();
}

// Small test 1
{
Expression<Func<string, int, char>> getCharAtIndex = (text, index) => text.ElementAt(index);
MethodCallExpression body = (MethodCallExpression)getCharAtIndex.Body;

var del2 = clonedExp.Compile();
var c2 = del2(inputString, 2);
// Serialize and deserialize delegate
SerializerConfig config = new SerializerConfig();
ExpressionFormatterResolver.Configure(config);
var ceras = new CerasSerializer(config);

Console.WriteLine();

// Can we make an expression to accelerate writing to readonly fields?
/*
var data = ceras.Serialize<object>(body);
var dataAsStr = Encoding.ASCII.GetString(data).Replace('\0', ' ');

var clonedExp = (MethodCallExpression)ceras.Deserialize<object>(data);
}

// Small test 2
{
var p = new ReadonlyTestClass("abc");
Expression<Func<string, int, char>> getCharAtIndex = (text, index) => (text.ElementAt(index).ToString() + text[index])[0];

var del = getCharAtIndex.Compile();

string inputString = "abcde";
char c1 = del(inputString, 2);

var f = p.GetType().GetField("_name", BindingFlags.Instance | BindingFlags.NonPublic);
var objParam = Expression.Parameter(p.GetType(), "obj");
var newValParam = Expression.Parameter(typeof(string), "newVal");

var fieldExp = Expression.Field(objParam, f);
// var assignment = Expression.Assign(fieldExp, newValParam);
// Serialize and deserialize delegate
SerializerConfig config = new SerializerConfig();

ExpressionFormatterResolver.Configure(config);


var assignmentOp = typeof(Expression).Assembly.GetType("System.Linq.Expressions.AssignBinaryExpression");
var assignment = (Expression)Activator.CreateInstance(assignmentOp,
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance,
null,
new object[] { fieldExp, newValParam },
null);
var ceras = new CerasSerializer(config);

// Create the method
var data = ceras.Serialize<object>(getCharAtIndex);
var dataAsStr = Encoding.ASCII.GetString(data).Replace('\0', ' ');

DynamicMethod dynamicMethod = new DynamicMethod("test", null, new Type[] { typeof(ReadonlyTestClass), typeof(string) }, owner: typeof(ReadonlyTestClass), skipVisibility: true);
var ilGen = dynamicMethod.GetILGenerator();
var clonedExp = (Expression<Func<string, int, char>>)ceras.Deserialize<object>(data);

MethodBuilder methodBuilder = ;
Expression.Lambda<Action<ReadonlyTestClass, string>>(assignment, objParam, newValParam).CompileToMethod(methodBuilder);
var del2 = clonedExp.Compile();
var c2 = del2(inputString, 2);

del(p, "changed!");
Debug.Assert(c1 == c2);

Console.WriteLine();
}
*/
}


Expand Down
10 changes: 5 additions & 5 deletions src/Ceras/Config/TypeConfig.cs
Expand Up @@ -200,11 +200,11 @@ public TypeConfig ConstructByUninitialized()

#region Formatter

public TypeConfig SetFormatter()
{
// We want to be able to use a specific formatter.
// For ReadonlyCollection we want to use DynamicObjectFormatter<> with a custom config instead of anything produced by the ICollectionFormatter
}
//public TypeConfig SetFormatter()
//{
// // We want to be able to use a specific formatter.
// // For ReadonlyCollection we want to use DynamicObjectFormatter<> with a custom config instead of anything produced by the ICollectionFormatter
//}

#endregion

Expand Down
1 change: 0 additions & 1 deletion src/Ceras/Formatters/ExpressionFormatters.cs
Expand Up @@ -67,7 +67,6 @@ internal static void Configure(SerializerConfig config)
if (type.FullName.StartsWith("System.Runtime.CompilerServices.TrueReadOnlyCollection"))
{
t.SetFormatter(); // DynamicObjectFormatter or resolver, how to make it public? Maybe set by formatter-type?
t.ConstructByUninitialized();
t.SetReadonlyHandling(ReadonlyFieldHandling.ForcedOverwrite);
t.SetTargetMembers(TargetMember.PrivateFields);
Expand Down
31 changes: 21 additions & 10 deletions src/Ceras/Formatters/MemberInfoFormatter.cs
@@ -1,13 +1,17 @@
namespace Ceras.Formatters
{
using Helpers;
using System;
using System.Reflection;

// parameter count can be merged into the unused bits of bindingData, but so much bit-packing makes things more complicated than they need to be;
// it's extremely unlikely that anyone would notice the savings, even if they'd serialize tons of MemberInfos

class MemberInfoFormatter<T> : IFormatter<T> where T : MemberInfo
{
IFormatter<string> _stringFormatter;
IFormatter<Type> _typeFormatter;

const BindingFlags BindingAllStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
const BindingFlags BindingAllInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

Expand All @@ -23,7 +27,7 @@ public void Serialize(ref byte[] buffer, ref int offset, T member)
_typeFormatter.Serialize(ref buffer, ref offset, member.DeclaringType);

byte bindingData = 0;

switch (member.MemberType)
{
// Write all the data we need to resolve overloads
Expand All @@ -39,9 +43,6 @@ public void Serialize(ref byte[] buffer, ref int offset, T member)
// 2. Method Name
_stringFormatter.Serialize(ref buffer, ref offset, method.Name);

// todo: parameter count can be merged into the unused bits of bindingData, but so much bit-packing makes things more complicated than they need to be;
// it's extremely unlikely that anyone would notice the savings, even if they'd serialize tons of MemberInfos

// 3. Parameters
var args = method.GetParameters();
SerializerBinary.WriteInt32(ref buffer, ref offset, args.Length);
Expand All @@ -52,9 +53,9 @@ public void Serialize(ref byte[] buffer, ref int offset, T member)

case MemberTypes.Property:
PropertyInfo prop = (PropertyInfo)(MemberInfo)member;

bindingData = PackBindingData(prop.GetAccessors(true)[0].IsStatic, ReflectionTypeToCeras(member.MemberType));

// 1. Binding data
SerializerBinary.WriteByte(ref buffer, ref offset, bindingData);

Expand Down Expand Up @@ -122,8 +123,18 @@ public void Deserialize(byte[] buffer, ref int offset, ref T member)
if (memberType == MemberType.Constructor)
member = (T)(MemberInfo)type.GetConstructor(bindingFlags, null, args, null);
else
member = (T)(MemberInfo)type.GetMethod(name, bindingFlags, binder: null, types: args, modifiers: null);

{
// todo: add a full "isGenericMethod" flag to the binding information, support open and half open definitions...
var resolvedMethod = ReflectionHelper.ResolveMethod(type, name, args);

if (resolvedMethod != null)
{
member = (T)(MemberInfo)resolvedMethod;
return;
}
}

throw new AmbiguousMatchException($"Can't resolve method named '{name}' with '{numArgs}' arguments.");
break;

case MemberType.Field:
Expand Down Expand Up @@ -175,7 +186,7 @@ static void UnpackBindingData(byte b, out bool isStatic, out MemberType memberTy

isStatic = (b & msbMask) != 0;

memberType = (MemberType) (b & ~msbMask);
memberType = (MemberType)(b & ~msbMask);
}
}

Expand Down

0 comments on commit 5c10f30

Please sign in to comment.