Problem with creating classes as runtime - PLEASE HELP

J

julia_beresford

Hi

I need to create the following class at runtime:

public class MyCollection : CollectionBase
{
public void Add(MyItem item)
{
List.Add(item);
}
public MyItem this[int index]
{
get
{
return (MyItem)List[index];
}
}
}

(the type MyType will also be of a type created at runtime with the
TypeBuilder)

I thought a good plan would be to create such a class and use the
ILDASM.exe tool on it to look at the IL generated. I got this (for
the Add method):

.method public hidebysig instance void
Add(string item) cil managed
{
// Code size 15 (0xf)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: call instance class
[mscorlib]System.Collections.IList [mscorlib]
System.Collections.CollectionBase::get_List()
IL_0007: ldarg.1
IL_0008: callvirt instance int32
[mscorlib]System.Collections.IList::Add(object)
IL_000d: pop
IL_000e: ret
} // end of method Class1::Add

and the code i've tried to write looks a little something like this:

methodBuilder = typeBuilder.DefineMethod("Add",
MethodAttributes.Public, null, new Type[] { collectionType } );
ILout = methodBuilder.GetILGenerator();
ILout.Emit(OpCodes.Nop);
ILout.Emit(OpCodes.Ldarg, 0);
ILout.Emit(OpCodes.Call, ????????);
ILout.Emit(OpCodes.Ldarg, 1);
ILout.Emit(OpCodes.Callvirt,
typeof(System.Collections.IList).GetMethod("Add", new Type[]
{typeof(Object)}));
ILout.Emit(OpCodes.Pop);
ILout.Emit(OpCodes.Ret);

You can see I've had a few problems with the OpCodes.Call - can anyone
help me please fill in the gaps?

Also, how do you interpret the following (IL_0013: br.s
IL_0015) to code, in this example:

.method public hidebysig specialname instance string
get_Item(int32 index) cil managed
{
// Code size 23 (0x17)
.maxstack 2
.locals init ([0] string CS$1$0000)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: call instance class
[mscorlib]System.Collections.IList
[mscorlib]System.Collections.CollectionBase::get_List()
IL_0007: ldarg.1
IL_0008: callvirt instance object
[mscorlib]System.Collections.IList::get_Item(int32)
IL_000d: castclass [mscorlib]System.String
IL_0012: stloc.0
IL_0013: br.s IL_0015

IL_0015: ldloc.0
IL_0016: ret
} // end of method Class1::get_Item

If anyone can help me i'd be very grateful.

Many thanks

Julia.
 
M

Marc Gravell

Can I ask... why?

In 2.0 generics would be the answer; I assume this is 1.1?

You won't be able to write code against a runtime-only class, so a lot
of the type-safety goes out of the window anyway unless you are using
a ton of interfaces. The only useful property, then, is the typed
indexer (which can be used to infer metadata for lists) - but in this
case, and with 1.1, the ITypedList interface might be a better
option...

So what is this class required *for*? As this might change how best to
do it...

Marc
 
M

Marc Gravell

Another note; I find Lutz Roeder's .NET Reflector much easier to use
for reading IL; give it a whirl (it is free).

For the "call" methods, you need the MethodInfo object that you want
to invoke; some examples here (unlreated subject, and .Net 2.0 based,
but the Emit section should be similar):
http://www.codeproject.com/csharp/HyperPropertyDescriptor.asp

Finally - are you compiling in debug mode? For reverse engineering IL,
I strongly suggest using release mode... those nops are looking mighty
suspicious ;-)

Marc
 
M

Marc Gravell

br.s is "branch, short form" - but it seems to transfer to the next
instruction anyway... perhaps see if it disappears in release mode...
I might try leaving it out... see what happens ;-p The runtime is very
good at spotting major goofs (stack abuse etc) - and minor gotchas
should show through testing.

And another thing : are you always handling classes? If you also deal
in structs then the IL changes to accomodate boxing and unboxing; see
that previous reference for an illustration.

Marc
 
J

julia_beresford

Ok...

Thanks for your reply and all the info, Generics looks interesting,
i'll look into that. Also i'll look into IL generated in release mode
- just for my own better understanding if nothing else!!! Hopefully
something you've suggested will allow me to move forward.

Just for your information the reason I want to do this is as
follows...

I want to parse an XMLSchema object, create runtime classes to
represent the data and set a propertyGrid control's SelectedObject to
an instance of the class. The user can then populate the propertyGrid
with data. Then I can serialize the runtime class to create my XML
file that conforms to the schema I was originally given.

The reason for the Collection class is for nested elements in the XML
schema. I've had some success, but please, if you think this is just
crazy, or if you can suggest a better way to do this, please say. I'm
always ready to listen to better ways of doing stuff!

Many thanks

Julia
 
M

Marc Gravell

Well, it *really* depends on your runtime. If you are 2.0 (or above),
then *absolutely* generics will simplify most of the collection side
of things; heck, List<T> does everything there and more; all you'd
need to do is use reflection to define T for an unknown (at
compile-time), which is easy enough.

However, I wonder if you are doing things the damned hard way...
creating a type is not the only way to define "properties" (quoted, as
I don't necessarily mean *class* properties); the
System.ComponentModel (used by all well-behaved binding controls)
allows you to declare virtual properties at runtime.

A little while ago (2nd May, "How to emulate missing property in
object", with a "Long post warning!") I posted a complete example of
this - a property-bag implementation that allowed you *at runtime* to
declare virtual properties against a type, using a bag to hold the
values. They don't appear in intellisense (because this is the model),
but they appear the view. Honest ;-p I have a more complete
implementation of this that supports multiple classes (and a likewise
list), but it is quite lengthy. But it perhaps easier to follow than
Reflection.Emit.

Of course, a third alternative is to build a DataSet that represents
the data; I'm not much of a DataSet fan myself, but for this type of
scenario it may be very useful - same concept: add properties at
runtime (but to an Instance (DataTable), not a Type as in my example).

Marc
 
M

Marc Gravell

One other off-the-wall thought... perhaps another pragmatic approach
here is to use "xsd.exe" to parse the schema, and then use "csc.exe"
to compile the resultant class/dataset to a temp assembly... load it
and then treat as before. I will use Reflection.Emit when pushed, but
I try to look at simpler solutions first, because my debugging time is
valuable to me, and I know my limitations; I'm an IL dabbler - not an
expect.

csc is part of the runtime, but unfortunately xsd is part of Visual
Studio and isn't listed in redist.txt, so not sure how you'd stand re
using it off your dev box...
 
J

julia_beresford

I'm having a lot of success with the Generics approach, so thank you
for pointing me in that direction. It's new in version 2.0 and i
didn't even know it existed, it's very interesting.

The xsd.exe tool is a good idea but i have had problems in the past
where the classes generated are not 100% correct - although this was
on a very lengthy complicated schema. I'm going to run some tests on
my example schemas because I like the idea of having a backup plan
should i run into more issues with the Emit stuff.

And yes, i too avoid using DataSets, and i'm not even sure why. I
must have had a bad experience with one once :-s

Thank you again for your help and suggestions.

Julia.
 
N

normk

I independently thought of Marc's suggestion of using XSD /c to
generate a class file before reading down to his comments and I agree
with him. If your schemas are somewhat stable (say on a daily basis
at least) then it's trivial to create classes using xsd /c. In
VS2003, it created some cluttered source code but in VS2005, it's very
clean and easy to incorporate into other projects. I would suck in
the generated code and perhaps even pipe the output of shell executing
xsd /c into either a stream or a directory that has a File System
Watcher http://msdn2.microsoft.com/en-us/library/system.io.filesystemwatcher.aspx
attached so that it automatically reads the generated source and makes
the class available in your source code. The watcher is also pretty
simple to integrate into your app.

I was also thinking that this is something that would be cool for the
Jasper team to take up in addition to dynamically creating entity
classes out of Sql Databases. Another neat idea would be to push the
XML schema into Sql Server automatically using the
SQLXMLBULKLOADLib.SQLXMLBulkLoad4Class. That way, you could expose
your C# class as a web service [WebMethod] and use the generated
schema (a la hibernate) as your repository for the incoming data or
the outgoing response data.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top