Olá pessoal, como estamos?
Já tem algum tempo que não escrevia nada nessa série. Então, hoje voltamos a ativa.
No post de hoje apresento as alterações que realizei em nosso projeto para suportar a criação de tipos dinamicamente.
Por favor, busque o código fonte atualizado em https://github.com/elemarjr/FluentIL.
Suportando a criação de tipos (sem qualquer implementação)
A primeira modificação que fiz no projeto foi incluir uma nova classe (chamada DynamicTypeInfo). Essa classe deverá prover a lógica para criação de tipos por Emitting. Observe os testes que escrevi inicialmente:
[Test]
public void AsType_ReturnsTypeWithSpecifiedName()
{
// arrange
var dt = new DynamicTypeInfo("Foo");
// act
var t = dt.AsType;
// assert
t.Name.Should().Be("Foo");
}
[Test]
public void AsType_ReturnedTypeShouldBeInstantiableViaActivatorCreateInstance()
{
// arrange
var dt = new DynamicTypeInfo("Foo");
// act
var t = dt.AsType;
var obj = Activator.CreateInstance(t);
// assert
obj.GetType().Should().Be(t);
}
Como os testes revelam, crio uma instância de DynamicTypeInfo passando o nome da classe que desejo instanciar e utilizo uma propriedade chamada AsType que deverá retornar um tipo correspondente. Além disso, escrevi um outro teste para a infraestrutura que utilizo para criação de tipos. Observe:
[Test]
public void TypeBuilder_InvokedTwoTimes_ReturnsSameValue()
{
// arrange
var dt = new DynamicTypeInfo("Foo");
// act
var t1 = dt.TypeBuilder;
var t2 = dt.TypeBuilder;
// assert
t1.Should().Be(t2);
}
Como pode ver, a criação de tipos utiliza um TypeBuilder para emitir tipos. Estou garantindo que cada DynamicTypeInfo possua apenas um TypeBuilder associado. Eis o código gerado para DynamicTypeInfo:
public class DynamicTypeInfo
{
public string TypeName { get; private set; }
public DynamicTypeInfo(string typeName)
{
this.TypeName = typeName;
}
TypeBuilder TypeBuilderField = null;
public TypeBuilder TypeBuilder
{
get
{
if (this.TypeBuilderField == null)
{
var assemblyName = new AssemblyName(
string.Format("__assembly__{0}", DateTime.Now.Millisecond)
);
var assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.RunAndSave
);
var moduleBuilder = assemblyBuilder.DefineDynamicModule(
assemblyBuilder.GetName().Name,
false
);
this.TypeBuilderField = moduleBuilder.DefineType(this.TypeName,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
typeof(object),
_interfaces.ToArray()
);
}
return this.TypeBuilderField;
}
}
List<Type> _interfaces = new List<Type>();
public DynamicTypeInfo Implements<TInterface>()
{
_interfaces.Add(typeof(TInterface));
return this;
}
public DynamicMethodInfo WithMethod(string methodName)
{
return new DynamicMethodInfo(this, methodName);
}
public Type AsType
{
get
{
return this.TypeBuilder.CreateType();
}
}
}
Testes passaram. Obviamente, você está percebendo a presença de outros métodos nessa classe. O método Implements, provê uma interface fluente para “informar” que uma interface deve ser implementada. O método WithMethod cria uma instância (modificada) da classe DynamicMethodInfo que apresentei nos posts anteriores.
Tornando DynamicMethodInfo/DynamicMethodBody compatível com DynamicTypeInfo
Até aqui, criávamos métodos dinâmicos, independentes de um tipo. Temos que modificar isso. A primeira ação é permitir que um DynamicTypeInfo seja informado. Além disso, todo método deverá possuir um nome. Observe o novo construtor:
public DynamicMethodInfo(
DynamicTypeInfo dynamicTypeInfo,
string methodName
)
: this()
{
this.DynamicTypeInfo = dynamicTypeInfo;
this.MethodName = methodName;
}
public DynamicMethodInfo()
{
this.Body = new DynamicMethodBody(this);
this.MethodName = "DynMethod";
}
Bonito! Criei uma propriedade para armazenar o nome do método. Outra coisa importante é alterar o acesso ao ILGenerator para considerar as duas vias possívels: DynamicMethod e MethodInfo. Observe:
DynamicMethod _result;
public DynamicMethod AsDynamicMethod
{
get
{
if (this.DynamicTypeInfo != null)
throw new InvalidOperationException();
if (_result == null)
{
var parameterTypes = _Parameters.Select(p => p.Type)
.ToArray();
_result = new DynamicMethod(
this.MethodName,
ReturnType,
parameterTypes
);
var ilgen = _result.GetILGenerator();
foreach (var variable in this.Variables)
ilgen.DeclareLocal(variable.Type);
}
return _result;
}
}
internal DynamicTypeInfo DynamicTypeInfo { get; private set; }
MethodBuilder methodBuilder = null;
public ILGenerator GetILGenerator()
{
if (this.DynamicTypeInfo == null)
return this.AsDynamicMethod.GetILGenerator();
else
{
if (this.methodBuilder == null)
{
var parameterTypes = _Parameters.Select(p => p.Type)
.ToArray();
methodBuilder = this.DynamicTypeInfo.TypeBuilder.DefineMethod(
this.MethodName,
MethodAttributes.Public | MethodAttributes.Virtual,
CallingConventions.HasThis,
ReturnType,
parameterTypes);
}
return this.methodBuilder.GetILGenerator();
}
}
Primeiro, perceba que nego o retorno para AsDynamicMethod quando há um DynamicTypeInfo. Além disso, implemento GetILGenerator para prover o ILGenerator adequado conforme o contexto. Esse método passa a ser usado em todo os Emittings.
Outra modificação relevante é no helper LdArg. Como métodos de instância possuem “this” no índice 0, modifiquei o helper LdArg que recebe um “nome”. Observe;
public DynamicMethodBody Ldarg(params string[] args)
{
var parameters = _Info.Parameters.ToArray();
uint offset = (uint) (_Info.DynamicTypeInfo != null ? 1 : 0);
foreach (var arg in args)
for (uint i = 0; i < parameters.Length; i++)
if (parameters[i].Name == arg)
Ldarg(i + offset);
return this;
}
Basicamente, verifico se há um “DynamicTypeInfo” especificado. Em caso positivo, adiciono um offset de 1.
Também criei uma propriedade em DynamicMethodBody chamada AsType para melhorar a fluência. Essa propriedade “acessa” a propriedade AsType do DynamicTypeInfo associado. Nesse mesmo propósito, adicionei um método NewMethod. Observe:
public Type AsType
{
get
{
return _Info.DynamicTypeInfo.AsType;
}
}
public DynamicMethodInfo WithMethod(string methodName)
{
return this._Info.DynamicTypeInfo.WithMethod(methodName);
}
Onde chegamos?!
FluentIL avançou um bocado. Agora temos condições de definir facilmente novos tipos, bem como métodos associados. Observe:
[Test]
public void CreateTypeWithOneMethod()
{
// arrange
var t = IL.NewType("One")
.WithMethod("Method").Returns(typeof(void))
.Ret()
.AsType;
var instance = Activator.CreateInstance(t);
instance.GetType().Name.Should().Be("One");
instance.GetType().GetMethod("Method").Should().Not.Be(null);
}
[Test]
public void TwoPlusTwoWithNamedParameters()
{
var t = IL.NewType()
.WithMethod("Add")
.WithParameter(typeof(int), "a")
.WithParameter(typeof(int), "b")
.Returns(typeof(int))
.Ldarg("a")
.Ldarg("b")
.Add()
.Ret()
.AsType;
var instance = Activator.CreateInstance(t);
var mi = instance.GetType().GetMethod("Add");
var result = mi.Invoke(instance, new object[] { 2, 2 });
result.Should().Be(4);
}
[Test]
public void ClassWithMultAndDivMethods()
{
var t = IL.NewType()
.WithMethod("Add")
.WithParameter(typeof(int), "a")
.WithParameter(typeof(int), "b")
.Returns(typeof(int))
.Ldarg("a")
.Ldarg("b")
.Add()
.Ret()
.WithMethod("Mul")
.WithParameter(typeof(int), "a")
.WithParameter(typeof(int), "b")
.Returns(typeof(int))
.Ldarg("a")
.Ldarg("b")
.Mul()
.Ret()
.AsType;
var instance = Activator.CreateInstance(t);
var miAdd = instance.GetType().GetMethod("Add");
var result = miAdd.Invoke(instance, new object[] { 2, 3 });
var miMult = instance.GetType().GetMethod("Mul");
result = miMult.Invoke(instance, new object[] { 2, 3 });
result.Should().Be(6);
}
Bonito, nossa interface já permite a geração de tipos, on-the-fly, com alguma elegância e expressividade (considerando que estamos usando IL).
Consideremos agora uma interface simples:
public interface IFoo
{
int Add(int a, int b);
int Mul(int a, int b);
}
Perfeito. Escrevendo um teste para mostrar nosso suporte a interfaces …
[Test]
public void ClassWithMultAndDivMethodsImplementingInterface()
{
var t = IL.NewType()
.Implements<IFoo>()
.WithMethod("Add")
.WithParameter(typeof(int), "a")
.WithParameter(typeof(int), "b")
.Returns(typeof(int))
.Ldarg("a")
.Ldarg("b")
.Add()
.Ret()
.WithMethod("Mul")
.WithParameter(typeof(int), "a")
.WithParameter(typeof(int), "b")
.Returns(typeof(int))
.Ldarg("a")
.Ldarg("b")
.Mul()
.Ret()
.AsType;
var instance = (IFoo)Activator.CreateInstance(t);
instance.Add(2, 3).Should().Be(5);
instance.Mul(2, 3).Should().Be(6);
}
Lindo e tudo funciona.
Agora sim… por hoje, era isso
![]()






maio 2nd, 2011 → 22:25
[...] post anterior, apresentei uma ampliação significativa na DSL. Com ela, é possível gerar tipos inteiramente [...]
maio 4th, 2011 → 2:00
[...] parte 4, apresentei uma ampliação significativa na DSL. Com ela, é possível gerar tipos inteiramente [...]