SharpCrafters Forum – How to instrument a generic type.

SharpCrafters Support Forum

        


How to instrument a generic type. Expand / Collapse
Author
Message
Posted 2/3/2010 10:00:30 AM


Community Member
Hi.
I have this attribute, which when applied to a type renames the backing field of each and every automatic property, so that the new field name is XML friendly.
The problem is that this attribute does not work for generic types. When I iterate over the affected types (using AnnotationRepositoryTask), I notice that the generic type definition is returned, and gets instrumented, but the final assembly is wrong.
The code is simple:
 
  void IAdviceProvider.ProvideAdvices(CodeWeaver codeWeaver)
   {
     var customAttributeDictionary = AnnotationRepositoryTask.GetTask(Project);
     var customAttributeEnumerator = customAttributeDictionary.GetAnnotationsOfType(typeof(RenameAutoPropertyBackingFieldAttribute), false);

     const string BackingFieldNameOverhead = "<>k__BackingField";

     // For each instance of our RenameAutoPropertyBackingFieldAttribute.
     var typeEnumeratorHelper = new TypeEnumeratorHelper(Project);
     while (customAttributeEnumerator.MoveNext())
     {
       foreach (var typeDef in typeEnumeratorHelper.GetTypes(customAttributeEnumerator.Current.TargetElement as TypeDefDeclaration))
       {
         if (IsStaticType(typeDef) || !IsSerializableType(typeDef))
         {
           continue;
         }

         foreach (var fieldDef in typeDef.Fields)
         {
           if (fieldDef.Name.StartsWith("<", StringComparison.Ordinal) &&
             fieldDef.Name.EndsWith(">k__BackingField", StringComparison.Ordinal))
           {
             fieldDef.Name = string.Format(CultureInfo.InvariantCulture, "_{0}_k__BackingField",
               fieldDef.Name.Substring(1, fieldDef.Name.Length - BackingFieldNameOverhead.Length));
           }
         }
       }
     }
   }

Where TypeEnumeratorHelper is my class, which yields the given type as well as all the derived types and nested types recursively. This way the aspect gets applied to the type, its nested types, the derived types and their nested types.

Anyway, my problem is that when the attribute is applied to a generic type, then the final code (after weaving) is invalid.

What am I missing?

Thanks.
Post #691
Posted 2/8/2010 8:19:29 AM


Community Member
Bumpy bump...
Post #692
Posted 2/9/2010 7:56:38 AM


Gael Fraiteur

SharpCrafters
Well, welcome to the world of generics... It's the single most difficult thing in MSIL.

The problem is probably not in the code you've shown, but in one of the instructions (ldfld, stfld) accessing the field.

You can't (never!) access a generic FieldDef->TypeDef directly, always through a FieldRef->TypeSpec->TypeDef. You can use the class GenericHelper to map a FieldDef to the canonical generic FieldRef.

Good luck.

-gael
Post #693
Posted 6/2/2010 9:23:21 PM


Community Member
Hi Gael.

Sorry to bother you again on this subject, but I finally want to clear this issue.

I have changed the code like so:

<FONT size=2 face=Consolas><FONT size=2 face=Consolas>

</FONT></FONT><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas>IEnumerable</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas> fields;</FONT></FONT>

<FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas>if</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas> (typeDef.IsGenericDefinition)

{

  fields = </FONT></FONT><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas>GenericHelper</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas>.GetTypeCanonicalGenericInstance(typeDef).Fields;

}

</FONT></FONT><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas>else</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas>

{

  fields = typeDef.Fields;

}

</FONT></FONT><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas>foreach</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas> (</FONT></FONT><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas>IField</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas> field </FONT></FONT><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas>in</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas> fields)

{

</FONT></FONT><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas>  if</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas> (field.Name.StartsWith(</FONT></FONT><FONT color=#a31515 size=2 face=Consolas><FONT color=#a31515 size=2 face=Consolas><FONT color=#a31515 size=2 face=Consolas>"<"</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas>, </FONT></FONT><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas>StringComparison</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas>.Ordinal) &&

      field.Name.EndsWith(</FONT></FONT><FONT color=#a31515 size=2 face=Consolas><FONT color=#a31515 size=2 face=Consolas><FONT color=#a31515 size=2 face=Consolas>">k__BackingField"</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas>, </FONT></FONT><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas>StringComparison</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas>.Ordinal))

  {

</FONT></FONT><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas>    var</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas> newName = </FONT></FONT><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas>string</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas>.Format(</FONT></FONT><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas>CultureInfo</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas>.InvariantCulture, </FONT></FONT><FONT color=#a31515 size=2 face=Consolas><FONT color=#a31515 size=2 face=Consolas><FONT color=#a31515 size=2 face=Consolas>"_{0}_k__BackingField"</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas>,

    field.Name.Substring(1, field.Name.Length - BackingFieldNameOverhead.Length));

</FONT></FONT><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas>    var</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas> fieldDef = field </FONT></FONT><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas>as</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas> </FONT></FONT><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas>FieldDefDeclaration</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas>;

</FONT></FONT><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas>    if</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas> (fieldDef != </FONT></FONT><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas>null</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas>)

    {

      fieldDef.Name = newName;

    }

</FONT></FONT><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas><FONT color=#0000ff size=2 face=Consolas>    else</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas>

    {

      ((</FONT></FONT><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas><FONT color=#2b91af size=2 face=Consolas>FieldRefDeclaration</FONT></FONT></FONT><FONT size=2 face=Consolas><FONT size=2 face=Consolas>)field).Name = newName;

    }

  }

}

</FONT></FONT>


Still this does not work out as expected. A generic type instrumented like so is badly formed.

Your advice is greatly appreciated.
Post #4675
Posted 6/3/2010 1:15:02 PM


Gael Fraiteur

SharpCrafters
I dont know what you're trying to achieve, but getting the collection of fields of a TypeSpec rarely makes sense.

I would rather talk to you 15 min on Skype than doing this on the forum. Please send me an email with how/when to find you.

-g
Post #4678
Posted 6/3/2010 7:24:15 PM


Community Member
Hi Gael.

Thanks for the prompt reply and the will to help personally. Let me try and explain what I want to do, this is a really simple aspect.

First, a few words of introduction.
As you surely know, the name of the backing field of an automatic property is not XML friendly. This is clearly seen when an object with automatic properties is serialized by WCF framework to be transmitted using SOAP. One gets first chance XmlException for each automatic property, which is handled internally by WCF. On catching this exception, WCF changes the serialization scheme of the particular backing field name to escape all the non XML friendly characters. This must have negative impact on the performance as well as it really annoys during debugging.

I have written a simple [RenameAutoPropertyBackingField] PostSharp.Core aspect, which simply replaces the <> characters in the backing field name to _. The aspect works on the attributed type as well as all the derived and nested types. BUT, it does not work with generic types.

Now you see, this is a really simple aspect, which I find quite useful.

Does it make any sense to you?

Thanks.

P.S.
How do I update the Log4PostSharp code base? Can I commit my changes to google code using the access code you provided me months ago?
Post #4695
Posted 6/3/2010 8:23:03 PM


Gael Fraiteur

SharpCrafters
Log4PostSharp: yes you can, I dont follow this project anyway.

Your issue: the problem is that a reference to a generic field uses the name of the field, so if you change the field name in the FieldDef you should also change it in the FieldRef.

So say you have a field reference MyClass<!0>.field, you actually have a FieldRef whose parent type is a TypeSpec (MyClass<!0>), whose parent is the TypeDef.

So, when you change the fields in a generic TypeDef, you should enumerate all its TypeSpecs, enumerate the FieldRefs of the TypeSpec, and do the same renamings there. Otherwise you break the relationship between the FieldRef and the FieldDef.

So let's summarize:

1. Enumerate all TypeDefs (module.GetDeclarationEnumerator(TypeDef)), rename fields.

2. Enumerate all TypeSpecs (module.TypeSpecs)

3. Filter TypeSpecs where ITypeSignature is GenericTypeInstanceTypeSignature, and GenericTypeInstanceTypeSignature.GenericDefinition is a TypeDef

4. Enumerate all FieldRefs of filtered TypeSpecs and change the Name field so that it matches the name of the FieldDef.

And that's it.

As you can see, generics in MSIL are very complex. I should probably invoice 200$/h for consulting on this .

-g
Post #4696
Posted 6/9/2010 7:35:25 PM


Community Member
Thanks.
I have managed to solve my issue like this:


foreach (IField field in typeDef.Fields)
{
 RenameAutoPropertyBackingField(field);
}

if (typeDef.IsGenericDefinition)
{
 foreach (IField field in GenericHelper.GetTypeCanonicalGenericInstance(typeDef).Fields)
 {
   RenameAutoPropertyBackingField(field);
 }
}


Looks like it finally works.

Thanks again.
Post #4736
Posted 6/10/2010 8:33:22 AM


Gael Fraiteur

SharpCrafters
You're right. It works by accident, because accessors of automatic property only access the field on the canonical instance. If you were renaming other fields, it would not work.
Post #4739
Posted 6/10/2010 12:32:01 PM


Community Member
Thanks. I will keep it in mind, when and if I need to rename other fields.
Post #4741
« Prev Topic | Next Topic »


All times are GMT +1:00, Time now is 11:34am

Powered By InstantForum.NET v4.1.4 © 2010
Execution: 0.231. 8 queries. Compression Disabled.