Skip to content

Commit

Permalink
Add tests for DynamicTypeGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
satano committed Jan 18, 2020
1 parent f2b2a6e commit f58c714
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 6 deletions.
114 changes: 114 additions & 0 deletions src/Mapster.Tests/DynamicTypeGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using Mapster.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using System;

namespace Mapster.Tests
{
[TestClass]
public class DynamicTypeGeneratorTests
{

private interface INotVisibleInterface
{
int Id { get; set; }
string Name { get; set; }
}

public interface ISimpleInterface
{
int Id { get; set; }
string Name { get; set; }
}

public interface IInheritedInterface : ISimpleInterface
{
int Value { get; set; }
}

public interface IComplexBaseInterface
{
int Id { get; set; }
void BaseMethod();
}

public interface IComplexInterface : IComplexBaseInterface
{
string Name { get; set; }
int ReadOnlyProp { get; }
int WriteOnlyProp { set; }
void SimpleMethod();
int ComplexMethod(byte b, ref int i, out string s);
}

[TestMethod]
public void ThrowExceptionWhenInterfaceIsNotVisible()
{
void action() => DynamicTypeGenerator.GetTypeForInterface(typeof(INotVisibleInterface));

var ex = Should.Throw<InvalidOperationException>(action);
ex.Message.ShouldContain("not accessible");
}

[TestMethod]
public void CreateTypeForSimpleInterface()
{
Type iClass = DynamicTypeGenerator.GetTypeForInterface(typeof(ISimpleInterface));

ISimpleInterface instance = (ISimpleInterface)Activator.CreateInstance(iClass);

instance.ShouldNotBeNull();
instance.ShouldBeAssignableTo<ISimpleInterface>();

instance.Id = 42;
instance.Name = "Lorem ipsum";

instance.Id.ShouldBe(42);
instance.Name.ShouldBe("Lorem ipsum");
}

[TestMethod]
public void CreateTypeForInheritedInterface()
{
Type iClass = DynamicTypeGenerator.GetTypeForInterface(typeof(IInheritedInterface));

IInheritedInterface instance = (IInheritedInterface)Activator.CreateInstance(iClass);

instance.ShouldNotBeNull();
instance.ShouldBeAssignableTo<IInheritedInterface>();

instance.Id = 42;
instance.Name = "Lorem ipsum";
instance.Value = 24;

instance.Id.ShouldBe(42);
instance.Name.ShouldBe("Lorem ipsum");
instance.Value.ShouldBe(24);
}

[TestMethod]
public void CreateTypeForComplexInterface()
{
Type iClass = DynamicTypeGenerator.GetTypeForInterface(typeof(IComplexInterface));

IComplexInterface instance = (IComplexInterface)Activator.CreateInstance(iClass);

instance.ShouldNotBeNull();
instance.ShouldBeAssignableTo<IComplexInterface>();

instance.Id = 42;
instance.Name = "Lorem ipsum";
instance.WriteOnlyProp = 24;

instance.Id.ShouldBe(42);
instance.Name.ShouldBe("Lorem ipsum");
instance.ReadOnlyProp.ShouldBe(0);

int i = 0;
string s = null;
Should.Throw<NotImplementedException>(() => instance.BaseMethod(), "Call BaseMethod.");
Should.Throw<NotImplementedException>(() => instance.SimpleMethod(), "Call SimpleMethod.");
Should.Throw<NotImplementedException>(() => instance.ComplexMethod(123, ref i, out s), "Call ComplexMethod.");
}
}
}
73 changes: 67 additions & 6 deletions src/Mapster/Utils/DynamicTypeGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
Expand Down Expand Up @@ -45,11 +46,22 @@ private static void CheckInterfaceType(Type interfaceType)
private static Type CreateTypeForInterface(Type interfaceType)
{
TypeBuilder builder = _moduleBuilder.DefineType("GeneratedType_" + Interlocked.Increment(ref _generatedCounter));
builder.AddInterfaceImplementation(interfaceType);

foreach (PropertyInfo prop in interfaceType.GetProperties())
foreach (Type currentInterface in GetAllInterfaces(interfaceType))
{
CreateProperty(interfaceType, builder, prop);
builder.AddInterfaceImplementation(currentInterface);
foreach (PropertyInfo prop in currentInterface.GetProperties())
{
CreateProperty(currentInterface, builder, prop);
}
foreach (MethodInfo method in currentInterface.GetMethods())
{
// MethodAttributes.SpecialName are methods for property getters and setters.
if (!method.Attributes.HasFlag(MethodAttributes.SpecialName))
{
CreateMethod(builder, method);
}
}
}

#if NETSTANDARD2_0
Expand All @@ -59,9 +71,33 @@ private static Type CreateTypeForInterface(Type interfaceType)
#endif
}

// GetProperties() and GetMethods() do not return properties/methods from parent interfaces,
// so we need to process every one of them.
private static IEnumerable<Type> GetAllInterfaces(Type interfaceType)
{
var allInterfaces = new List<Type>();
var interfaceQueue = new Queue<Type>();
allInterfaces.Add(interfaceType);
interfaceQueue.Enqueue(interfaceType);
while (interfaceQueue.Count > 0)
{
var currentInterface = interfaceQueue.Dequeue();
foreach (var subInterface in currentInterface.GetInterfaces())
{
if (allInterfaces.Contains(subInterface))
{
continue;
}
allInterfaces.Add(subInterface);
interfaceQueue.Enqueue(subInterface);
}
}
return allInterfaces;
}

private static void CreateProperty(Type interfaceType, TypeBuilder builder, PropertyInfo prop)
{
const BindingFlags interfacePropMethodsFlags = BindingFlags.Instance | BindingFlags.Public;
const BindingFlags interfacePropMethodFlags = BindingFlags.Instance | BindingFlags.Public;
// The property set and get methods require a special set of attributes.
const MethodAttributes classPropMethodAttrs
= MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
Expand All @@ -79,7 +115,7 @@ private static void CreateProperty(Type interfaceType, TypeBuilder builder, Prop
propGetIL.Emit(OpCodes.Ldfld, propField);
propGetIL.Emit(OpCodes.Ret);

MethodInfo interfaceGetMethod = interfaceType.GetMethod(getMethodName, interfacePropMethodsFlags);
MethodInfo interfaceGetMethod = interfaceType.GetMethod(getMethodName, interfacePropMethodFlags);
builder.DefineMethodOverride(propGet, interfaceGetMethod);
propBuilder.SetGetMethod(propGet);
}
Expand All @@ -95,10 +131,35 @@ private static void CreateProperty(Type interfaceType, TypeBuilder builder, Prop
propSetIL.Emit(OpCodes.Stfld, propField);
propSetIL.Emit(OpCodes.Ret);

MethodInfo interfaceSetMethod = interfaceType.GetMethod(setMethodName, interfacePropMethodsFlags);
MethodInfo interfaceSetMethod = interfaceType.GetMethod(setMethodName, interfacePropMethodFlags);
builder.DefineMethodOverride(propSet, interfaceSetMethod);
propBuilder.SetSetMethod(propSet);
}
}

private static void CreateMethod(TypeBuilder builder, MethodInfo interfaceMethod)
{
Type[] parameterTypes = null;
ParameterInfo[] parameters = interfaceMethod.GetParameters();
if (parameters.Length > 0)
{
parameterTypes = new Type[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
parameterTypes[i] = parameters[i].ParameterType;
}
}

MethodBuilder classMethod = builder.DefineMethod(
interfaceMethod.Name,
MethodAttributes.Public | MethodAttributes.Virtual,
interfaceMethod.CallingConvention,
interfaceMethod.ReturnType,
parameterTypes);
ILGenerator classMethodIL = classMethod.GetILGenerator();
classMethodIL.ThrowException(typeof(NotImplementedException));

builder.DefineMethodOverride(classMethod, interfaceMethod);
}
}
}

0 comments on commit f58c714

Please sign in to comment.