How to create a generic instance

1,217 views
Skip to first unread message

seesharper

unread,
May 8, 2011, 4:56:20 AM5/8/11
to mono-cecil
Hi!

I don't understand how to emit the code to create an instance of a
generic type.

Mono.Cecil 0.9.4.0


I will illustrate this with a very simple example.

This is the code that I want to emit.

public void TestNull(int value)
{
Nullable<int> test = value;
}

NOTE:

I don't know that the generic argument is an int at the time of
emitting the code.

I need to read the parameter type and create a Nullable<T> according
to the parameter type.

This means that I CAN'T do something like:

MethodReference ctor = _moduleDefinition.Import(typeof
(Nullable<int>).GetConstructor(new Type[] {typeof (int)}));


This is the IL code that needs to be emitted.

.maxstack 2
.locals init (
[0] valuetype [mscorlib]System.Nullable`1<int32> test)
L_0000: nop
L_0001: ldloca.s test
L_0003: ldarg.1
L_0004: call instance void
[mscorlib]System.Nullable`1<int32>::.ctor(!0)
L_0009: nop
L_000a: ret


So this is what I got so far.

First a create a GenericInstanceType:

var nullableType = _moduleDefinition.Import(typeof(Nullable<>));
var genericType =
nullableType.MakeGenericInstanceType(_moduleDefinition.Import(typeof(int)));

(yeah, I used the Cecil.Rocks extension method for creating the
generic instance type, but that is not the issue here.)

The int would be the type from the value parameter of the TestNull
method.

Now I have a GenericInstanceType that represents Nullable<int>.

But how do I create a MethodReference that represents the constructor
passed to OpCodes.NewObj?

The GenericInstanceType which is derived from MethodReference does not
have a collection of methods so I can't get it from there either.

Next I tried something like this:

MethodReference constructorReference = new MethodReference(".ctor",
_moduleDefinition.Import(typeof(void)), genericType);

No luck on this either.

A lot of forum posts suggests that the methodreference should be added
to the MemberReferences collection of the module.

But that is a read-only collection available from the
GetMemberReferences method.



I must be missing something?

Any help on this issue would be great!!!

Regards

Bernhard Richter














Gábor Kozár

unread,
May 8, 2011, 6:11:47 AM5/8/11
to mono-...@googlegroups.com
Hi,

I've had the same issue before, but I've managed to solve it with the help of Jb.

What you need to do is the following:

TypeReference genericArg = _moduleDefinition.TypeSystem.Int32;
GenericInstanceType genericType =  _moduleDefinition.Import(typeof(Nullable<>)) .MakeGenericInstanceType(genericArg);
MethodDefinition ctor = genericType.Resolve().Methods.First(m => m.IsConstructor && m.Parameters.Count == 1);

Note that if you try to emit a newobj instruction with 'ctor' with its operand at this point, you'll get invalid IL, because when you use Resolve(), you lose all generic argument information. We need to provide that manually.
For that, you have to add your generic argument to ctor.DeclaringType, or alternatively, you can use Rocks' MakeGenericInstanceType, like so:

ctor.DeclaringType = ctor.DeclaringType.MakeGenericInstanceType(genericArg);

Now you can emit your newobj instruction with 'ctor' being its argument. Note that you _might_ need to clone ctor.DeclaringType first (I'm not sure, I don't have my code in front of me atm), but you get the general idea.

Hope this helps!

2011/5/8 seesharper <bernhard...@gmail.com>
--
--
mono-cecil

seesharper

unread,
May 8, 2011, 12:27:44 PM5/8/11
to mono-cecil
Hi Gábor and thanks for you help!!

Sadly it did not quite work out.

The ctor.DeclaringType is a TypeDefinition and I can't assign the
result of

ctor.DeclaringType.MakeGenericInstanceType(genericArg)

If I do ctor.DeclaringType.MakeGenericInstanceType(genericArg).Resolve
it will turn my generic type into a
TypeDefiniton with all its generic type information lost. In other
words back to where I started.

Any thoughts on this?

Regards

Bernhard Richter







On May 8, 12:11 pm, Gábor Kozár <kozarga...@gmail.com> wrote:
> Hi,
>
> I've had the same issue before, but I've managed to solve it with the help
> of Jb.
>
> What you need to do is the following:
>
> TypeReference genericArg = _moduleDefinition.TypeSystem.Int32;
> GenericInstanceType genericType =
> _moduleDefinition.Import(typeof(Nullable<>))
> .MakeGenericInstanceType(genericArg);
> MethodDefinition ctor = genericType.Resolve().Methods.First(m =>
> m.IsConstructor && m.Parameters.Count == 1);
>
> Note that if you try to emit a newobj instruction with 'ctor' with its
> operand at this point, you'll get invalid IL, because when you use
> Resolve(), you lose all generic argument information. We need to provide
> that manually.
> For that, you have to add your generic argument to ctor.DeclaringType, or
> alternatively, you can use Rocks' MakeGenericInstanceType, like so:
>
> ctor.DeclaringType = ctor.DeclaringType.MakeGenericInstanceType(genericArg);
>
> Now you can emit your newobj instruction with 'ctor' being its argument.
> Note that you _might_ need to clone ctor.DeclaringType first (I'm not sure,
> I don't have my code in front of me atm), but you get the general idea.
>
> Hope this helps!
>
> 2011/5/8 seesharper <bernhard.rich...@gmail.com>

Gábor Kozár

unread,
May 8, 2011, 2:19:29 PM5/8/11
to mono-...@googlegroups.com
Hey,

Resolve() will make you lose all your generic arguments. If you can't change the DeclaringType, then you have to clone the entire method. I have an extension method for this, I believe it's also somewhere in Cecil.Rocks (maybe under a different name).

public static MethodReference MakeHostInstanceGeneric(this MethodReference self, params TypeReference[] arguments)
{
      var reference = new MethodReference(self.Name, self.ReturnType, self.DeclaringType.MakeGenericInstanceType(arguments))
      {
          HasThis = self.HasThis,
          ExplicitThis = self.ExplicitThis,
          CallingConvention = self.CallingConvention
      };

      foreach (var parameter in self.Parameters)
          reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));

      foreach (var generic_parameter in self.GenericParameters)
          reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));

      return reference;
}

So your code:

TypeReference genericArg = _moduleDefinition.TypeSystem.Int32;
GenericInstanceType genericType = _moduleDefinition.Import(typeof(Nullable<>)).MakeGenericInstanceType(genericArg);
MethodReference ctor = genericType.Resolve().Methods.First(m => m.IsConstructor && m.Parameters.Count == 1).MakeHostInstanceGeneric(genericArg);

And now you should be able to emit your call instruction.

2011/5/8 seesharper <bernhard...@gmail.com>
--
--
mono-cecil

seesharper

unread,
May 8, 2011, 2:39:51 PM5/8/11
to mono-cecil
Hi again and we are getting closer and closer.

I just can't believe how difficult it is to work with generics.

It looks right now, but PEVerify throws an exception

[MD]: Error: Signature has an invalid token (token: 0x0200031d;
offset: 0x00000002). [token:0x1B000001]
[MD]: Error: Signature has generic type of arity 0 instantiated at
different arity 1 at byte=0x00000005. [token:0x1B000001]
[IL]: Error: [C:\Users\Bernhard\Documents\Visual Studio 2010\Projects
\MethodCaching\TestResults\Tests[1]\Out\SampleLibrary.dll :
SampleLibrary.SampleClass::Calculate][mdToken=0x6000001][offset
0x00000001] Unable to resolve token.
3 Error(s) Verifying SampleLibrary.dll

Could I ask you a BIG favor?

Could you try this out yourself and see if it works on your side?

I have been ripping my hair out for two days over this :)


//Bernhard
> 2011/5/8 seesharper <bernhard.rich...@gmail.com>

Gábor Kozár

unread,
May 8, 2011, 3:07:10 PM5/8/11
to mono-...@googlegroups.com
Sorry, in my last e-mail I mentioned a 'call' instruction: you need the 'newobj' instruction.

Also, I'm stupid: we first need to import the MethodReference before you're actually able to use it. So your code is:

TypeReference genericArg = _moduleDefinition.TypeSystem.Int32;
GenericInstanceType genericType = _moduleDefinition.Import(typeof(Nullable<>)).MakeGenericInstanceType(genericArg);
MethodReference ctor = _moduleDefinition.Import(genericType.Resolve().Methods.First(m => m.IsConstructor && m.Parameters.Count == 1).MakeHostInstanceGeneric(genericArg));

Now it should be fine, it almost exactly matches the code in my application. Sorry, it was my mistake. :(

2011/5/8 seesharper <bernhard...@gmail.com>
--
--
mono-cecil

seesharper

unread,
May 8, 2011, 3:24:12 PM5/8/11
to mono-cecil
And it works!!!!!!!!!!!!!!!

Thanks a lot for your help! You are a true life saver :)

I did not find the extension method in Cecil.Rocks.

Is it okay if I use your method(MakeHostInstanceGeneric) in my code?

//bernhard





On May 8, 9:07 pm, Gábor Kozár <kozarga...@gmail.com> wrote:
> Sorry, in my last e-mail I mentioned a 'call' instruction: you need the
> 'newobj' instruction.
>
> Also, I'm stupid: we first need to import the MethodReference before you're
> actually able to use it. So your code is:
>
> TypeReference genericArg = _moduleDefinition.TypeSystem.Int32;
> GenericInstanceType genericType =
> _moduleDefinition.Import(typeof(Nullable<>)).MakeGenericInstanceType(generi cArg);
> MethodReference ctor =
> _moduleDefinition.Import(genericType.Resolve().Methods.First(m =>
> m.IsConstructor && m.Parameters.Count ==
> 1).MakeHostInstanceGeneric(genericArg));
>
> Now it should be fine, it almost exactly matches the code in my application.
> Sorry, it was my mistake. :(
>
> 2011/5/8 seesharper <bernhard.rich...@gmail.com>

Gábor Kozár

unread,
May 8, 2011, 3:35:50 PM5/8/11
to mono-...@googlegroups.com
You're welcome. :) Sure, go ahead and use it.

I just wrote a couple of extension methods that should make it easy to use generics: http://pastebin.com/BTX3AvpV
I have quickly ported my application to use them, and everything appears to be working - but please do not take it for granted.

They should reduce your code to:

TypeReference genericArg = _moduleDefinition.TypeSystem.Int32;
GenericInstanceType genericType = _moduleDefinition.Import(typeof(Nullable<>)).MakeGenericInstanceType(genericArg);
MethodReference ctor = _moduleDefinition.Import(genericType.ReferenceMethod(m => m.IsConstructor && m.Parameters.Count == 1));

I hope that's better. :) Just keep in mind that Resolve() will drop all your generic arguments, and all will be fine.

Jb might want to consider adding them to Cecil.Rocks - I'm quite sure numerous people would either welcome them, or have already written their own. :)

2011/5/8 seesharper <bernhard...@gmail.com>
--
--
mono-cecil

seesharper

unread,
May 13, 2011, 3:40:24 AM5/13/11
to mono-cecil
Thanks a lot for your help!

Here is what I needed this stuff for anyway.

http://www.codeproject.com/KB/cs/MethodCaching.aspx

Credit goes out to you at the end of the article.

Best regards

Bernhard Richter

On May 8, 9:35 pm, Gábor Kozár <kozarga...@gmail.com> wrote:
> You're welcome. :) Sure, go ahead and use it.
>
> I just wrote a couple of extension methods that should make it easy to use
> generics:http://pastebin.com/BTX3AvpV
> I have quickly ported my application to use them, and everything appears to
> be working - but please do not take it for granted.
>
> They should reduce your code to:
>
> TypeReference genericArg = _moduleDefinition.TypeSystem.Int32;
> GenericInstanceType genericType =
> _moduleDefinition.Import(typeof(Nullable<>)).MakeGenericInstanceType(generi cArg);
> MethodReference ctor =
> _moduleDefinition.Import(genericType.ReferenceMethod(m => m.IsConstructor &&
> m.Parameters.Count == 1));
>
> I hope that's better. :) Just keep in mind that Resolve() will drop all your
> generic arguments, and all will be fine.
>
> Jb might want to consider adding them to Cecil.Rocks - I'm quite sure
> numerous people would either welcome them, or have already written their
> own. :)
>
> 2011/5/8 seesharper <bernhard.rich...@gmail.com>
> ...
>
> read more »

Jb Evain

unread,
May 13, 2011, 3:49:01 AM5/13/11
to mono-...@googlegroups.com
On Fri, May 13, 2011 at 9:40 AM, seesharper <bernhard...@gmail.com> wrote:
> Here is what I needed this stuff for anyway.
>
> http://www.codeproject.com/KB/cs/MethodCaching.aspx

Very nice article, congrats!

Jb

Gábor Kozár

unread,
May 13, 2011, 8:06:06 AM5/13/11
to mono-...@googlegroups.com
Very nice article indeed, congratulations!

Ironically, it was the very same thing - method caching - that got me into Cecil, although now I'm working on something more like PostSharp. Perhaps I'll release its source code when I get somewhere with it (i.e. it actually becomes useful).

Thanks for the mention in the credits! I'm honored!

2011/5/13 seesharper <bernhard...@gmail.com>
--
--
mono-cecil

seesharper

unread,
May 19, 2011, 7:29:52 AM5/19/11
to mono-cecil
Thanks a lot, Jb

On May 13, 9:49 am, Jb Evain <jbev...@gmail.com> wrote:
Reply all
Reply to author
Forward
0 new messages