Calling generic methods

549 views
Skip to first unread message

Jean-Paul Mayer

unread,
May 22, 2011, 1:10:41 PM5/22/11
to mono-...@googlegroups.com

I am working on a problem where I want to replace a method on a class with
a new method.

I want to transform this:

public class Foo {
public int Bar(){
return 0;
}
};

into this:

public class Foo {
public int _Call_Bar(string a, string b){
return 0;
}

public int Bar(string a, string b){
try {
//do some preprocessing
return _Call_Bar(a, b);
} finally {
//post processing
}
}
}

I have it mostly working. However, when Foo has a generic parameter (IE,
public class Foo<T>) then the generated IL looks to be incorrect.

I end up with a call to

L_000d: call instance int32 Demo.Foo`1::_Call_Bar(!T, !T)

However, when I try to use this new type in an assembly it fails with a
type load exception (PEVerify says the assembly is verified).

I tried to look at the compiler generated IL as if I had do the
transformation manually, and it ends up with a slightly different call


L_000d: call instance int32 Demo.Foo`1<!T>::_Call_Bar(!T, !T)


It looks like the call doesn't know anything about the generic type of Foo?

How can I say that the call to _Call_Bar is for type Foo<T>?

Thanks,

JP

Gábor Kozár

unread,
May 22, 2011, 4:08:30 PM5/22/11
to mono-...@googlegroups.com
This problem has been discussed several times already.
Check out this discussion:


In short: when you Resolve() a TypeReference or MethodReference (or any other reference), you loose all generic arguments. You have to then restore those generic arguments by essentially cloning the MethodReference (in your case), except setting its DeclaringType to the original TypeReference (which still contains the generic arguments).
The discussion above contains several extension methods that will make this easier for you, but it's important that you know the concept.

2011/5/22 Jean-Paul Mayer <may...@errantworks.com>
--
--
mono-cecil

Stanislav

unread,
May 24, 2011, 1:49:17 AM5/24/11
to mono-cecil
> when you Resolve() a TypeReference or MethodReference (or any
> other reference), you loose all generic arguments. You have to then restore
> those generic arguments by essentially cloning the MethodReference (in your
> case), except setting its DeclaringType to the original TypeReference (which
> still contains the generic arguments).


It looks like bug, not as feature )

Jb Evain

unread,
May 24, 2011, 2:58:58 AM5/24/11
to mono-...@googlegroups.com
On Tue, May 24, 2011 at 7:49 AM, Stanislav <sunex.de...@gmail.com> wrote:
> It looks like bug, not as feature )

Wrong.

Jb

Gábor Kozár

unread,
May 24, 2011, 6:12:24 AM5/24/11
to mono-...@googlegroups.com
I agree, it's not very intuitive, but that's how it works. You may write extension methods, or use ones that I've shown in the discussion to ease the problem.
If you can implement it better, please go ahead.

2011/5/24 Stanislav <sunex.de...@gmail.com>
--
--
mono-cecil

Stanislav

unread,
May 24, 2011, 11:23:22 PM5/24/11
to mono-cecil
No, no, all is ok )))) Thank you very much for great help!

On 24 май, 14:12, Gábor Kozár <kozarga...@gmail.com> wrote:
> I agree, it's not very intuitive, but that's how it works. You may write
> extension methods, or use ones that I've shown in the discussion to ease the
> problem.
> If you can implement it better, please go ahead.
>
> 2011/5/24 Stanislav <sunex.developm...@gmail.com>

Stanislav

unread,
May 25, 2011, 1:32:46 AM5/25/11
to mono-cecil
I have a problem making generics. I cannot insert nongeneric class
into generic class as nested. For ex:

Orriginally I have class GenericClass<T>

public class Something
{
public class GenericClass<T>
{
public SomeProperty<T> { get; set; }
}
}
but when I try to insert new nested class into GenericClass<T>, I
whant to get result:

public class Something
{
public class GenericClass<T>
{
private static class Utils
{
private static Utils() { /* ... */ }
public static string field;
}
public SomeProperty<T> {
get { return field; }
set { /* ... */ }
}
}
}

But in SomeProperty getter I have wrong accessing to GenericClass
field:

Something.GenericClass<>.Utils.field;

generic class GenericClass<> is not instanciated by type.

How do I add nested type (here I use Gabor Kozar's method
MakeGenericSelf http://pastebin.com/BTX3AvpV)?

var genericOwnerType = declaringType.HasGenericParameters ?
declaringType.MakeGenericSelf() : null;
var resolvedType = genericOwnerType == null ?
declaringType.Resolve() : genericOwnerType.Resolve();

utils = new TypeDefinition(declaringType.FullName, "<>_Utils",
TypeAttributes.NestedAssembly |
TypeAttributes.Sealed | TypeAttributes.Class |
TypeAttributes.Abstract,
declaringType.Module.GetType<Object>())
{
DeclaringType = resolvedType
};

resolvedType.NestedTypes.Add(utils);
var utilsCctor = new MethodDefinition(".cctor",
MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName |
MethodAttributes.HideBySig |
MethodAttributes.Static,
declaringType.Module.GetVoidType());

utilsCctor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
utils.Methods.Add(utilsCctor);
m_utilsClasses.Add(declaringType, utils);

Whats wrong?

Gábor Kozár

unread,
May 25, 2011, 5:32:54 AM5/25/11
to mono-...@googlegroups.com
What is your MakeGenericSelf() extension method? I did not write it, I'm quite positive about it (the linked code only contains various Reference* extension methods, that I did write).

Anyway, I see only one problem on this side (although you haven't shown the code which emits the IL that actually uses the Utils class you inject here):

utils = new TypeDefinition(declaringType.FullName, "<>_Utils", TypeAttributes.NestedAssembly | TypeAttributes.Sealed | TypeAttributes.Class | TypeAttributes.Abstract, declaringType.Module.GetType<Object>())
       {
             DeclaringType = resolvedType // here! DeclaringType should be set to the original, generic TypeReference, NOT the resolved TypeDefinition
       };

2011/5/25 Stanislav <sunex.de...@gmail.com>
--
--
mono-cecil

Stanislav

unread,
May 25, 2011, 5:50:49 AM5/25/11
to mono-cecil
:) My mistake :))

public static GenericInstanceType MakeGenericSelf(this
TypeReference typeRef)
{
var genType = new GenericInstanceType(typeRef);
foreach (var p in typeRef.GenericParameters)
genType.GenericArguments.Add(p);
return genType;
}

On 25 май, 13:32, Gábor Kozár <kozarga...@gmail.com> wrote:
> What is your MakeGenericSelf() extension method? I did not write it, I'm
> quite positive about it (the linked code only contains various Reference*
> extension methods, that I did write).
>
> Anyway, I see only one problem on this side (although you haven't shown the
> code which emits the IL that actually uses the Utils class you inject here):
>
> utils = new TypeDefinition(declaringType.FullName, "<>_Utils",
> TypeAttributes.NestedAssembly | TypeAttributes.Sealed | TypeAttributes.Class
> | TypeAttributes.Abstract, declaringType.Module.GetType<Object>())
>        {
>              DeclaringType = resolvedType // here! DeclaringType should be
> set to the original, generic TypeReference, NOT the resolved TypeDefinition
>        };
>
> 2011/5/25 Stanislav <sunex.developm...@gmail.com>
>
>
>
>
>
>
>
> > I have a problem making generics. I cannot insert nongeneric class
> > into generic class as nested. For ex:
>
> > Orriginally I have class GenericClass<T>
>
> >  public class Something
> >  {
> >    public class GenericClass<T>
> >    {
> >        public SomeProperty<T> { get; set; }
> >    }
> >  }
> > but when I try to insert new nested class into GenericClass<T>, I
> > whant to get result:
>
> >  public class Something
> >  {
> >    public class GenericClass<T>
> >    {
> >        private static class Utils
> >        {
> >            private static Utils() { /* ... */ }
> >            public static string field;
> >        }
> >        public SomeProperty<T> {
> >          get { return field; }
> >          set { /* ... */ }
> >        }
> >    }
> >  }
>
> > But in SomeProperty getter I have wrong accessing to GenericClass
> > field:
>
> > Something.GenericClass<>.Utils.field;
>
> > generic class GenericClass<> is not instanciated by type.
>
> > How do I add nested type (here I use Gabor Kozar's method
> > MakeGenericSelfhttp://pastebin.com/BTX3AvpV)?

Gábor Kozár

unread,
May 25, 2011, 6:03:43 AM5/25/11
to mono-...@googlegroups.com
Okay, that is VERY wrong.

GenericParameters and GenericArguments are two distinct things.
Let me explain it to you with a simple example.

Take this method definition:

public static void Stuff<T>()
{
     ...
}

If you get the MethodDefinition object representing this method declaration, it'll have a single item in GenericParameters: T. There are NO generic arguments here, because the method does not known (or care) about what type will be actually passed to it as T.
However, when you use this method:

Stuff<string>();

Here, in the IL, the 'call' instruction has an operand of type GenericInstanceMethod (which derives from TypeSpecification, which derives from TypeReference). It has the same GenericParameters as the definition above (single item: T), but it also has a GenericArguments property, whose items represent the types that were actually passed as type parameters to the method (in this case, as T).
So the GenericArguments collection should have a single item here: a reference to System.String.

In short: T is the generic parameter, whereas string is the generic argument. The same goes for types.
Hope it's clear now.

2011/5/25 Stanislav <sunex.de...@gmail.com>
--
--
mono-cecil

Stanislav

unread,
May 26, 2011, 2:58:28 AM5/26/11
to mono-cecil
In first thank you so much for so useful reply!

But I have next question:

1) If I have (for ex)

public static class First
{
public static class Second<T>
{
public static class Third
{
public static int field;
}
}
}

then really I have following:

public static class First
{
public static class Second<T>
{
public static class Third<T> <-- HERE
{
public static int _field;
}
}
}

AND when I need to have access to field "_field" do I need to
instanciate by generic only Third class type or I should instanciate
base classes too? May be I should put T generic parameter of Second<T>
as argument of Third?

On 25 май, 14:03, Gábor Kozár <kozarga...@gmail.com> wrote:
> Okay, that is VERY wrong.
>
> GenericParameters and GenericArguments are two distinct things.
> Let me explain it to you with a simple example.
>
> Take this method definition:
>
> public static void Stuff<T>()
> {
>      ...
>
> }
>
> If you get the MethodDefinition object representing this method declaration,
> it'll have a single item in GenericParameters: T. There are NO generic
> arguments here, because the method does not known (or care) about what type
> will be actually passed to it as T.
> However, when you use this method:
>
> Stuff<string>();
>
> Here, in the IL, the 'call' instruction has an operand of type
> GenericInstanceMethod (which derives from TypeSpecification, which derives
> from TypeReference). It has the same GenericParameters as the definition
> above (single item: T), but it also has a GenericArguments property, whose
> items represent the types that were actually passed as type parameters to
> the method (in this case, as T).
> So the GenericArguments collection should have a single item here: a
> reference to System.String.
>
> In short: T is the generic parameter, whereas string is the generic
> argument. The same goes for types.
> Hope it's clear now.
>
> 2011/5/25 Stanislav <sunex.developm...@gmail.com>

Gábor Kozár

unread,
May 26, 2011, 5:09:54 AM5/26/11
to mono-...@googlegroups.com
Okay, lets split up your questions. First consider this simpler example, without generics:

public class Outer
{
      public class Inner
      {
            public int field;
      }
}

In order to instantiate an object instance of the Inner class, you don't need to have an object instance of the Outer class. The two are not tied together in that fashion. So in C#, you write:

var obj = new Outer.Inner();
Console.WriteLine(obj.field);

Now on to generics.

public class Outer<T>
{
       public class Inner
       {
       }
}

In this case, members of the Inner class indeed have access to the 'T' type parameter, because you can instantiate the Inner type like this:

var obj = new Outer<string>.Inner();

(Where T will obviously have the type value of System.String.)

Now in your example, you're right that the Third class does have access to the type parameter 'T', because it is a nested type of Second<T>. Because Third is declared as a static class, you cannot instantiate it, but you can access its static field the following way:

Console.WriteLine(First.Second<string>.Third.field);

Now emitting the IL for this is not very trivial, you'll need to build some more extension methods (mine only cover the most basic things, they don't handle generic methods or nested types).
I would suggest you to compile example codes like these, and then check their IL source with a reflector or ildasm. That's the most effective way of learning.

2011/5/26 Stanislav <sunex.de...@gmail.com>
--
--
mono-cecil

Hendry Luk

unread,
Mar 7, 2012, 5:00:20 AM3/7/12
to mono-...@googlegroups.com
That trick works but only up to a certain point. But if your method use generic arguments, the  MakeHostInstanceGeneric helper does not seem to do the trick.

For instance:
public class SomeClass<T>
{
   public IEnumerable<T> SomeMethod(Func<T> blah)
   { /* ... */ }
}

The MakeHostInstanceGeneric only sanitises the DeclaringType of the MethodReference, but it does not sanitise method arguments, return-types, and so on. And since the generic argument can be wrapped within another generic type (e.g. IEnumerable<T> on the method above), the sanitation is not exactly trivial.

Using this unsanitised MethodReference (which still contains open genericparameter types), it does not seem to produce a valid OpCodes.CallVirt instruction.

Any idea how to solve this?

Cheers

--
--
mono-cecil

Hendry Luk

unread,
Mar 7, 2012, 5:18:32 AM3/7/12
to mono-...@googlegroups.com
Ouch sorry ignore me. CallVirt *does* work with MethodReferences that contain open genericparameter types. There was an error in my unit-tests.

Sorry
Reply all
Reply to author
Forward
0 new messages