.NET Framework - Creating a dynamic array...

Asked By Matthew Wells on 29-Aug-07 04:37 PM
Hello.

I have figured out how to create an instance of an object only knowing the
type by string.

string sName = "MyClassName";
Type t = Type.GetType(sName);
Object objNew = Activator.CreateInstance(t);

This works, but now I need to declare an array like

Object[] objNew = Activator.CreateInstance(t)[0];

This doesn't work.  Can anyone help?

Thanks.

Matthew Wells
Matthew.Wells@FirstByte.net




Jason Newell replied on 29-Aug-07 04:47 PM
Matt,

I believe this is what you are looking for.

Array array = Array.CreateInstance(type, length);

Jason Newell
Software Engineer
www.jasonnewell.net
Matthew Wells replied on 30-Aug-07 09:23 AM
Not quite.  This still returns an Array type, not an Object type. .  I need
to have the equivalent of these statements as if I knew what type I had to
start with.

public MyClass Person
{
string Name;
}

Person[] = new Person[2];

The problem is I don't know what type I will need.  The code you gave
returns an error when I try to assign a "person" to an elememt in the
array. - "Can't apply indexing with [] to an expression of type
'System.Array'"  I can't cast it because I don't know what type it is.

This is definitely towards the goal.  Any more ideas?
Marc Gravell replied on 30-Aug-07 09:43 AM
Hang on - originally you said you only knew the type as a string; you
will struggle to get much better than Array in this case (note that
Array has GetValue() and SetValue() to work in this case).

Another option might involve generics.

You can get a Type from a string using a few tricks;
Type.GetType(name) sometimes works, but it depends on where the class
is... you may need to enumerate a little...

From a Type, you can call a generic method using MakeGenericMethod().

Something like:

class Person { }
static class Program {
static void Main() {
Person[] typed = SomeMethod<Person>();
Person p1 = typed[3];

Type t = Type.GetType("Person");
Array untyped = (Array)
typeof(Program).GetMethod("SomeMethod").MakeGenericMethod(t).Invoke(null,
null);
Person p2 = (Person) untyped.GetValue(3);
}
public static T[] SomeMethod<T>() where T : class, new() {
T[] data = new T[10];
for (int i = 0; i < 10; i++) {
data[i] = new T();
}
return data;
}
}
Jason Newell replied on 30-Aug-07 10:17 AM
Matt,

Have a look at this approach then.

ArrayList arrayList = new ArrayList();
arrayList.Add(1);
arrayList.Add(5.3);
arrayList.Add(true);
arrayList.Add("Hello World");
object[] array = arrayList.ToArray();

Jason Newell
Software Engineer
www.jasonnewell.net
Matthew Wells replied on 30-Aug-07 11:31 AM
This is a little too much for what I need - and I don't think generics are
an option because I am also getting the type dynamically.

Type t = Type.GetType(sEntityName);
Object = objNew = Activator.CreateInstance(t);

This works to get one instance of my object, but in other cases I need to
get an array like

t[] MyList = new t[5]; - this fails  (I've also added t.GetType() and
typeof(t) )

I also tried

List<t> = new List<t>(); - this also fails - and with thte same options as
above.

I don't think this works because 't' is not strongly typed.

Why is this so difficult?
Jon Skeet [C# MVP] replied on 30-Aug-07 11:43 AM
On Aug 30, 4:31 pm, "Matthew Wells" <Matthew.We...@FirstByte.net>



Because the type information is largely there for the *compiler*.
You're trying to provide it at runtime.

Could you try to explain *why* you believe you need this? There's
almost certainly a better approach to the problem.

Jon
Matthew Wells replied on 30-Aug-07 12:52 PM
I'm reading metadata I've netered into an Access table that defines objects.
Each object can have primitive property types like string, int, etc, but can
also have other objects defined as properties.. Here is one brief example.

Customer (entity)
FirstName
LastName
Address (entity)
Contacts[] (entity) - this is an array of contacts.
Contact (entity)
Address (entity)


I'm transferring data from one system to another.  I have a recursive
function set up to read from the (in this case the customer table) create an
instance of the object, assign values to the properties, then create the
array and assign subentities to that and then assign the array to the parent
entity's property.  The theory works fine.  As I said, all the data is being
read from a table so I have no way of knowing what entities are needed ahead
of time. This works when I only need one instance of an entity (like the
Customer's Address entity) using

string sEntityName = "Contact"
Type t = Type.GetType(sEntityName);
Object = objNew = Activator.CreateInstance(t);

I've tried your suggestions of using an ArrayList and Array.CreateInstance.
These work so I can assign objects into them, but when it comes time to add
the array or list to the Customer entity, it fails because the Custoemr
entity is expecting an array of Contacts, not a generic Array or ArrayList.
I can't cast either because casting requires a known type.  I've tried
(t.GetType()) and (typeof(t)) without success.

Does this make it clearer?
Jon Skeet [C# MVP] replied on 30-Aug-07 02:00 PM
The way that the arrays are created with Jason's original code creates
the right type of array at runtime. Presumably you're using reflection
to set the property anyway, so it should be fine.

Could you post a short but complete program which demonstrates the
problem?

See http://www.pobox.com/~skeet/csharp/complete.html for details of
what I mean by that.


--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet   Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
Marc Gravell replied on 30-Aug-07 03:44 PM
Except of course that this is in response to a post that does
*precisely* this.

Marc
Matthew Wells replied on 30-Aug-07 04:53 PM
I got it.  The problem was in the type declaration.  I also had to use the
.GetConstructor method.  The type with an array is idfferent thatn

Type t = Type.GetType(stype) + "[]");
object objList = t.GetContructor(new Type[](typeof(int32))).invoke(new
Object[] (...element count...));

You can't assign to objList[0], but you can create a new object{] objListNew
and cast it to an array of objects.  Then I can still assign objList to the
main entity's property that is expecting an array of type t.
Jon Skeet [C# MVP] replied on 30-Aug-07 05:16 PM
What does that do that using Array.CreateInstance doesn't do rather
more simply?

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet   Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
Jason Newell replied on 30-Aug-07 05:19 PM
Matt,

I think non of us still get what you're asking for.  I would stab my
eyes out if I had to work on the code in your last reply.  I have a hard
time believing that there is not a more eloquent way to write the code,
but then again, I don't understand what you're after.

Surely you can come up with something better with this example.

Type t = typeof(string);  // Or whatever
ArrayList arrayList = new ArrayList(Array.CreateInstance(t, 0));
arrayList.Add("Hello");
arrayList.Add("World");
object[] objList = arrayList.ToArray();
objList[0] = "Goodbye";

Jason Newell
Software Engineer
www.jasonnewell.net
Jason Newell replied on 30-Aug-07 05:24 PM
I forgot to mention that you can also strongly type the array in my
previous example.

string[] objList = (string[])arrayList.ToArray(typeof(string));

Jason Newell
Software Engineer
www.jasonnewell.net
Matthew Wells replied on 01-Sep-07 11:24 AM
I did find a way.  The original problem was that I wanted an array of a
specific type, but I did not know the type until the statement was
executed..  I had previously figured out how to create a single instance:

string sMyType = "CustomerObject";
Type t = Type.GetType(sMyType);
Object obj = Activator.CreateInstance(t);

Like I said, this worked fine until I was trying to create an array of the
type.  I was using the line

Type t = Type.GetType(sMyType);

and then trying to create an array of that like

Object[] objList = Activator.CreateInstance(t);

This didn't work because I was still working with a type of a single
instance of sMyType.  I changed it to

Type t = Type.GetType(sMyType + "[]");

Now the original line for CreateInstance would not work because it was
trying to create an object using the default constructor with no parameters,
but the t[] type' constructor takes a parameter so I changed that line to:

Object[] objList = Activator.CreateInstance(t, new Object param[5]); \\ for
five elements.

(I think that was the syntax, I'm at home now and am not looking at the
code, but you get the idea).  The only problem here is that you can't assign
directly into objList like

objList[0] = "abc";  \\ this creates an error that I still don't fully
understand.

But you can create a new array, cast it and assign objList to it, and then
when you're done assigning the elements you can still refer to objList since
it's referring to the same object.

Object[] objNewList = (Object) objList[];

So now it's much cleaner.  What do you think?
Matthew Wells replied on 01-Sep-07 11:24 AM
I did find a way.  The original problem was that I wanted an array of a
specific type, but I did not know the type until the statement was
executed..  I had previously figured out how to create a single instance:

string sMyType = "CustomerObject";
Type t = Type.GetType(sMyType);
Object obj = Activator.CreateInstance(t);

Like I said, this worked fine until I was trying to create an array of the
type.  I was using the line

Type t = Type.GetType(sMyType);

and then trying to create an array of that like

Object[] objList = Activator.CreateInstance(t);

This didn't work because I was still working with a type of a single
instance of sMyType.  I changed it to

Type t = Type.GetType(sMyType + "[]");

Now the original line for CreateInstance would not work because it was
trying to create an object using the default constructor with no parameters,
but the t[] type' constructor takes a parameter so I changed that line to:

Object[] objList = Activator.CreateInstance(t, new Object param[5]); \\ for
five elements.

(I think that was the syntax, I'm at home now and am not looking at the
code, but you get the idea).  The only problem here is that you can't assign
directly into objList like

objList[0] = "abc";  \\ this creates an error that I still don't fully
understand.

But you can create a new array, cast it and assign objList to it, and then
when you're done assigning the elements you can still refer to objList since
it's referring to the same object.

Object[] objNewList = (Object) objList[];

So now it's much cleaner.  What do you think?
Matthew Wells replied on 01-Sep-07 11:25 AM
Remember that I cannot cast because I do not know the type ahead of time.
Jon Skeet [C# MVP] replied on 01-Sep-07 01:54 PM
Yes, involving this:

Type t = Type.GetType(sMyType + "[]");

and then using Activator.CreateInstance.

However, Array.CreateInstance had previously been suggested, and I
believe that would solve your problem more simply. What does it not do
that you needed it to do?

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet   Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
Matthew Wells replied on 01-Sep-07 04:55 PM
I did say that's what I'm using (this is a little corrected from my last
post - I checked the actual line)

Object objList = Activator.CreateInstance(t, new Object[1] {5}); \\ for five
elements

You'll notice that I'm now using the signature for CreatteInstance that is
needed for an array of type t.

But like I said before, if you use this, you can't assign a value to an
element of the array like:

objList[0] = 2;

You'll get an error.  You have to declare another array, cast it, and then
assign this array to that.

Object[] ObjListNew = (Object[])objList;

After you're done using ObjListNew, you can jsut refer to the original
objList since they refer to the same object.

Nice and neat.
Jon Skeet [C# MVP] replied on 01-Sep-07 05:10 PM
But you're still using *Activator*.CreateInstance rather than
*Array*.CreateInstance.

The call to Array.CreateInstance is simpler and more obviously
understandable, IMO.


All of that is still true with Array.CreateInstance, but you don't have
to go through as many hoops.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet   Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
Matthew Wells replied on 01-Sep-07 05:51 PM
I am creating an array of a specific type.  If I use Array.CreateInstance
and then try to assign as Object into an element I get

Error 2 Cannot apply indexing with [] to an expression of type
'System.Array'

Remember that I still can't cast becaue I don't know the type ahead of time.

You would think that the array is of type t becaust that's what it is
supposed to do.  I believe that Array.CreateInstance is creating a generic
Array that can hold the type t, not creating an array of t types.  I'm open
to suggestions.
Jon Skeet [C# MVP] replied on 01-Sep-07 06:15 PM
I'm not suggesting you remove *all* your code and replace it with a
call to Array.CreateInstance - just the bit that's calling
Activator.CreateInstance.

Activator.CreateInstance is declared to return Object, so you're
already having to cope with that - just cope with Array.CreateInstance
returning Array in exactly the same way. Cast it to object[] (for
reference types - that won't work for value type arrays) or whatever
you currently do.

Note that if you *just* need indexing, you can do:

IList array = Array.CreateInstance(...);

IList provides an indexer.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet   Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
Matthew Wells replied on 01-Sep-07 06:39 PM
Now I've got it all down.  It failed the first time I tried
Array.CreateInstance because I was still using my original

Type t = Type.CreateType(sEntityName + "[]");

so I was really creating an array of arrays.  I set it back to just the type
so the Array.CreateInstance was working, but I still couln't assign to it.
Your seggestion of IList worked!!!  Now I'm down to these two lines of code
and everything works perfectly.
Type t = Type.GetType(sEntityName);

IList objList = Array.CreateInstance(t, dt.Rows.Count);

Now here's where I ask too much.  I don't need this, but it would be great.
If I create a single instance of t:

Object objNew = Activator.CreateInstance(t);

...how can I get the intellisense to work?  For eexample, I create a
Customer object so when I type

objNew.

I see
.Name
.Address
... etc.

This one must be impossible.

Thanks to all of you for your help!!!!!
Jon Skeet [C# MVP] replied on 01-Sep-07 07:02 PM
You can't. That would be asking the IDE to know that you're talking
about Customer even though you've already said that you don't know
which type you *are* using!

As you say, it's impossible.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet   Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
Matthew Wells replied on 01-Sep-07 07:15 PM
Bummer.

Thanks again!!!

BTW, this is the longest thread I have vver been involved with.