Olá pessoal, tudo certo?
No último post dessa série, mostrei minha implementação para emitting de propriedades automáticas. No post de hoje, mostro como realizei a implementação para suportar propriedades “read-only” e com código no getter e setter.
Como de costume, lembre-se que todo o código-fonte está disponível no github.
Testes para propriedades “read-only”
Como de costume, antes de escrever qualquer código novo, vamos escrever algum código de teste:
[Test] public void EmitingTypeThatSupportsReadOnlyProperty() { var newType = IL.NewType() .Implements() .WithProperty("ReadOnlyProperty", typeof(int), (m) => m .Ldc(10) .Ret() ) .AsType; var f = (IFoo2)Activator.CreateInstance(newType); f.ReadOnlyProperty.Should().Be(10); } [Test] public void EmitingTypeThatSupportsReadOnlyPropertyWithoutInterface() { var newType = IL.NewType() .WithProperty("ReadOnlyProperty", typeof(int), (m) => m .Ldc(10) .Ret() ) .AsType; var f = Activator.CreateInstance(newType); var p = newType.GetProperty("ReadOnlyProperty"); p.GetValue(f, null).Should().Be(10); } public interface IFoo2 { int ReadOnlyProperty { get; } }
Como você pode perceber, assumo aqui a existência de um novo método: WithProperty em nossa interface fluente.
Implementando o método WithProperty para propriedades “read-only”
Agora que já temos nossos testes, hora de implantar o código. Vejamos o que escrevi:
public DynamicTypeInfo WithProperty( string propertyName, Type propertyType, Actiongetmethod ) { var property = this.TypeBuilder.DefineProperty( propertyName, PropertyAttributes.None, propertyType, new Type[] { } ); var get_methodinfo = this.WithMethod(string.Format("get_{0}", propertyName)) .TurnOnAttributes(MethodAttributes.RTSpecialName) .TurnOnAttributes(MethodAttributes.SpecialName); getmethod(get_methodinfo.Returns(propertyType)); property.SetGetMethod(get_methodinfo.MethodBuilder); return this; }
O terceiro parâmetro refere-se a um action, onde o cliente “realiza” a codificação IL do método (olhe o teste para tornar a coisa mais “evidente”).
Infelizmente, essa implementação também evidencia uma dívida técnica. Muito do código que escrevi está parecido com o que havia escreito no post anterior para propriedades automáticas.
Adicionando suporte para escrita
Já conseguimos adicionar propriedades somente leitura. Agora, queremos suporte para propriedades com suporte para leitura e escrita. Segue a implementação:
public DynamicTypeInfo WithProperty( string propertyName, Type propertyType, Actiongetmethod, Action setmethod = null ) { var property = this.TypeBuilder.DefineProperty( propertyName, PropertyAttributes.None, propertyType, new Type[] { } ); var get_methodinfo = this.WithMethod(string.Format("get_{0}", propertyName)) .TurnOnAttributes(MethodAttributes.RTSpecialName) .TurnOnAttributes(MethodAttributes.SpecialName); getmethod(get_methodinfo.Returns(propertyType)); property.SetGetMethod(get_methodinfo.MethodBuilder); if (setmethod != null) { var set_methodinfo = this.WithMethod(string.Format("set_{0}", propertyName)) .TurnOnAttributes(MethodAttributes.RTSpecialName) .TurnOnAttributes(MethodAttributes.SpecialName) .WithParameter(propertyType, "value"); setmethod(set_methodinfo.Returns(typeof(void))); property.SetSetMethod(set_methodinfo.MethodBuilder); } return this; }
Essa versão de WithProperty é praticamente idêntica a anterior, porém, agora, aceita também o envio do código para propriedade setter. Esse ajuste autoriza uma revisão do método que escrevemos no post anterior. Observe:
public DynamicTypeInfo WithAutoProperty( string propertyName, Type propertyType ) { string fieldName = string.Format("_{0}", Guid.NewGuid()); return this .WithField(fieldName, propertyType) .WithProperty( propertyName, propertyType, (mget) => mget .Ldarg(0) // this; .Ldfld(fieldName) .Ret(), (mset) => mset .Ldarg(0) // this; .Ldarg("value") .Stfld(fieldName) .Ret() ); }
Bem mais expressivo. Vamos aos testes?
Sucesso!
Alberto Monteiro
23/09/2011
Elemar,
Os ORM’s como EF e NHibernate criam classes proxy para controlar seu contexto ou sessão.
Esse tipo de abordagem é usando FluentIL?
elemarjr
23/09/2011
Olá Alberto,
Entendi que sua pergunta foi: “ORMs utilizam emitting para criar suas classes proxies?!”
Então, não posso afirmar com certeza se essa é a abordagem adotada. Entretanto, posso relacionar um projeto open-source, indicado a mim pelo @tucaz, que faz exatamente isso.
Dá uma olhada no dapper-dot-net (). Trata-se de “a simple object mapper for .Net”.
O fonte é curtinho e bem instrutivo.
[]s
Elemar JR