C# reflection : How to set nested Property of nested Lists - c#

Imagine following "path" :
MyObject.MyFirstList[0].MySecondList[0].MyProperty = "Hello"
MyProperty is of type String.
None of the types is known at compiletime, only runtime via reflection of assembly.
I have everything, but i can not set the second list on the first with SetValue. I always get either null-ref exceptions or "target type does not match".
What I have tried so far:
iteration 1:
var constructedList = Activator.CreateInstance(constructedListType);
target.GetType().GetProperty(propertyToAdd).SetValue(target, constructedList)
Iteration 2:
Same way, works as well. So now we have MyObject.MyFirstList[0].MySecondList[]
Iteration 3: TODO: Create instance of first object of MySecondList and set its MyProperty-property to the created property:
var target = Activator.CreateInstance(typeOfItem);
target.GetType().GetProperty(propertyToAdd)?.SetValue(target, null);
So to summarize the question:
This works:
someInstance.GetType().GetProperty(someProperty).SetValue(someInstance, objToSet);
Why does something like this not work? Or does it - if yes, how?
someInstance.GetType().GetProperty(someList.listInsideSomeList.finalProperty).SetValue(...);

In short, reflection provides you with means to get information on a type. It cannot be used to reference the property of another type (as shown in your last example).
You are on the right track, but you need to go from right to left in your path. So taken you have some nested properties. You first create the nested object, such as:
var objNested = Activator.CreateInstance(objNestedType);
objNestedType.GetProperty(nestedPropertyName).SetValue(objNested, value);
and then you add your newly created object to its 'parent':
var objBase = Activator.CreateInstance(objBaseType);
objBaseType.GetProperty(basePropertyName).SetValue(objBase, objNested);
Arrays are bit different. Reflection expects you to write down the final value when using propertyinfo.SetValue, in case of an array this is the entire array value. In order to do this you can use Array.CreateInstance(typeof(arrayType), arrayLength) to create a new array and use the 'SetValue(object, index)' method to set its contents.

Related

2 Generic list assignment

if class type list is there named
Collection<PurchaseOrderDetail> poDetails = new Collection<PurchaseOrderDetail>();
and another list with same type is there named _poH.PODetail
why _poH.PODetail = poDetails.ToList(); generates an error
Cannot implicitly convert type 'System.Collections.Generic.List'
to 'System.Collections.ObjectModel.Collection'
what is the solution for this, any explanation please.
All the reason behind the question is
_poH.PODetail = poDetails;
made poDetails.RemoveAt(Convert.ToInt32(e.RowIndex)); updates as well so I was searching for some thing like _poH.PODetail = poDetails.ToCollection();
According to the error message, _poH.PODetail is of type Collection, so assigning a list to it doesn’t work. But since poDetails is a collection itself, you can just assign it directly:
poH.PODetail = poDetails;
So you don’t actually need to call ToList() on it to convert it to a list.
There is no ToCollection method you could call on enumerables, but you could use the Collection constructor that takes a list to make it wrap that list and create a readonly collection:
new Collection(poDetails.ToList());
The short answer is simply that the ToList<T> extension returns an instance of List<T> class which, although similar, is not the same type as Collection<T>.
Basically this doesn't work for the same reasons you cannot set a string value to an integer variable.
One thing you can do though, is initializing the content of a new collection instance with an IList<T> instance. Therefore, the following should give you exactly what you want:
_poH.PODetail = new Collection(poDetails.ToList());
Also, as poke suggested, you might also want to assign the PODetail property with the poDetails variable itself.
_poH.PODetail = poDetails;
However, you must remember that Collection<T> is a reference type. This means that the objects in your collection won't be "copied" inside _poH.PODetail; instead, both poDetails and _poH.PODetail will be pointing to the exact same collection. Any changes done to one collection will automatically be reflected on the other.

How could I've known the cast to List<> makes sense?

For this code snippet below, which probably won't need an explanation to the persons who can answer my question:
protected readonly List<TEntity> Records; //Within the class declared.
protected virtual void ReadFile()
{
//So the file exists, is not null and not whitespace only
var serializer = new XmlSerializer(Records.GetType());
using (var reader = new StringReader(contents))
{
var records = (List<TEntity>)serializer.Deserialize(reader);
Records.Clear();
Records.AddRange(records);
}
}
It's about the line:
var records = (List<TEntity>)serializer.Deserialize(reader);
(1) How could I've known (deduced) that this cast was possible? I looked up that the Deserialize() methods returns the type "object". So that's can't be the hint. (Edit: I mean during coding/design time. Not compiling afterwards for trial and error. So think goal-wise: goal = store xml data into a list<>. Is it possible through a simple cast (yes) and how could I have known in advance?).
(2) How could I've / can I deduce(d) how variable "records" would end up? For example, what shouldn't make me think that only a single entry is written to the list and that single index holds all of the XML content? (as opposed to keeping a nice structure whilst writing it to the list)
The final goal of my question is to understand when such a cast is needed and especially how the actual cast makes sense. I'm wiling to put in effort in it with practice and experimentations. But I don't know how to think.
a) I"m a beginner, though learning fast i.m.o.
b) I Have read and understood implicit/explicit casting and understood that it's based on range, as opposed to data size. But those tutorials restrict to built in basic types like int, float, decimal (you name it). Now this problem domain (casting) I would like to move to a higher level.
The cast
var records = (List<TEntity>)serializer.Deserialize(reader);
works because of
new XmlSerializer(Records.GetType());
The cast from object to anything will always compile but yield a runtime exception when the types don't match.
The serializer knows about the root type from its constructor and will create a result of that type. If the XML doesn't match that, it will throw an error. You cannot deserialze a single TEntity with this, only a List<> with 0 or more elements.
You can verify it easily:
object temp = serializer.Deserialize(reader);
// print or inspect temp.GetType() here
var records = (List<TEntity>)temp;
Note that if XmlSerialzier had been designed as a generic type then the cast would not have been necessary:
var serializer = new XmlSerializerGeneric<List<TEntity>>();
var records = serializer.Deserialize(reader);
But it wasn't, it uses the older way of 'dynamic typing' through the System.Object class.
It would be much obvious if instead of this
var serializer = new XmlSerializer(Records.GetType());
you used this
var serializer = new XmlSerializer(typeof(List<TEntity>));
The rule is simple. What ever type you pass to the XmlSerializer constructor, you can safely cast the Deserialize result to that type.
The cast is needed because XmlSerializer (as well as Enum and many other framework classes) exists from a long time before the generics were added, so returning an object is the only choice it had. And it must remain this way because of backward compatibility (to not break the existing code you have written).
How could I've known (deduced) that this cast was possible?
One way is to use the debugger. Put a breakpoint on the Records.Clear(); line just after records is set. Then in either the Watch window or the Locals window, take a look at the type of the records variable. It might say something like this:
object { List<TEntity> }
records is a List<TEntity> but it's being passed as an object because the call to serializer.Deserialize(reader) returns an object. And if you didn't declare an implicit conversion from object to List<TEntity>, you have to use an explicit conversion.
I recommend against implicit conversions for all but the most obvious cases.
(1) How could I've known (deduced) that this cast was possible?
It all began here,
protected readonly List<TEntity> Records;
You don't have to deduce but you already know you are reading file contents from a file which has some XML with some sort of structure and since you are using a list you know upfront that there are going to be mulitple list items
I looked up that the Deserialize() methods returns the type
"object". So that's can't be the hint.
object class is not used directly (So basically that is a hint that you have to cast) and it must be casted to some type (in this case List<TEntity>) before it could be used.
(2) How could I've / can I deduce(d) how variable "records" would end
up?
Lets say I want to create list of persons, I would know I would keep some structure for example,
I have following XML
<persons>
<person>
<name>Chuck Norris</name>
<age>137</age>
</person>
<person>
<name>Donald Trump</name>
<age>19</age>
</person>
<persons>
You know you want to get the records of person your list would be declare like this, List<Person>. You know Deserialize() method returns object and you would want to read persons data, you would cast that object into List<Person>. So basically what I am trying to say is that both the question you asked are based on the assumption and choices made at the design time of you application.

Instantiating an array element

I'm new to C#, so this problem may be trivial to many of you...
I'm writing simple application that uses API of a certain website. I'm using and altering an example from this website.
So the problem is when I'm instantiating an object that (in simple words) returns simple variable like string, int or even user-defined type everything seems to be working fine, but when I try to instantiate an element that is an element array of user-defined:
request.#parameters.examplename = new somenamespace.SomeName.UserType();
type I get a message like this:
"Cannot implicitly convert type "somenamespace.SomeName.UserType' to 'somenamespace.SomeName.UserType[]'."
Can you explain to me what am I doing wrong? Is there a different way to instantiate an element array? If you need more information please let me know.
You should change your line to:
request.#parameters.examplename =
new somenamespace.SomeName.UserType[] { new somenamespace.SomeName.UserType() };
You are currently trying to assign a single UserType value to something that wants an array of UserType's. So this is solved by creating an array (the new[] part) and populating it with the UserType value (the {} part).
Given your error output it looks like your syntax is incorrect. You have a UserType array (UserType[]) called request.#parameters.examplename which you are attempting to assign a UserType object to; new UserType() calls the constructor on the object.
What you require is a newly instantiated array of UserTypes:
request.#parameters.examplename = new somenamespace.SomeName.UserType[];

C# return type of function

What does GetNode return, a copy or a reference to the real value?
public GraphNode GetNode(int idx)
{
return Nodes[idx];
}
In other words will this code change the real value or it will change a copy returned from GetNode?
GetNode(someIndex).ExtraInfo = something;
Thanks
Depending on wherever GraphNode is a class or struct. In case of a class you'll be changing "real" value. Struct is the opposite.
It depends on your definition of GraphNode.
If it is a class (by reference) it will return the same instance;
or if it is a struct (value-type) then you'll get a new instance.
In other words will this code change the real value or it will change
a copy returned from GetNode?
GetNode(someIndex).ExtraInfo = something;
If GetNode() returns a struct / value type you will actually get a compilation error in C#:
Cannot modify the return value of 'GetNode(someIndex)' because it is
not a variable
This is exactly because the compiler is trying to protect you from changing a copied value that goes nowhere. Your example makes only sense if GetNode() returns a reference type.
The only way to get the example to compile for a value type is by separating retrieval of the return value from the property assignment:
var node = GetNode(someIndex);
node.ExtraInfo = something;
In this case as other posters have mentioned it does depend on whether GetNode() returns a reference type or a value type. For a reference type you are changing the original class instance that GetNode() returns a reference to, for a value type you are modifying a new, copied instance.
The way classes work in C# is that they can be passed around as pointers.
You can set the values inside it, like you have in your example, and it will change the original.
Setting the actual class itself cannot be done without a wrapper though.
It will return a reference (in case GraphNode is a class) to a GraphNode object, so the original object will be altered.

How to create a value type or string type object at runtime using Reflection

Probably simple but could not figure out. I am loading an assembly at runtime and browsing through some classes and generating input controls for its properties. To create an instance of an object at runtime I am using:
object o = PropertyType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, Type.EmptyTypes,null).Invoke(null);
and it works fine for class types. When the type is an array, I use
object o = PropertyType.Type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { 0 });
which also works fine. But when it comes to string type or value types GetConstructor.Invoke does not work. I also tried Activator.CreateInstance which also did not work.
What you're running into is that value types don't really have parameterless constructors. C# makes it look like they do, but they don't at the CLR level.
Activator.CreateInstance should work fine for real value types though:
object o = Activator.CreateInstance(typeof(int));
Console.WriteLine(o); // Prints 0
This will always give the default value for any value type.
Now, you're asking about strings - what string would you expect to create? The default value for the string type is null - but would you want the empty string instead? If so, you'll need to special-case that code.
If you want to be able to instantiate arbitrary objects with particular values, one thing you can check is if there is a TypeConverter that supports converting an instance (with the value you want to match) to an InstanceDescriptor. I'm not entirely sure what you're doing (your example suggests you're trying to create 'default' instances of the different objects), but just thought I'd mention this in case it's relevant. I use this in Emit code to emit arbitrary constant values to the stack via IL. Here's the snippet that gets the InstanceDescriptor:
var converter = TypeDescriptor.GetConverter(value);
if (converter.CanConvertTo(typeof (InstanceDescriptor)))
{
var desc = (InstanceDescriptor) converter.ConvertTo(value, typeof (InstanceDescriptor));
}
The descriptor specifies a means of constructing the instance, which could be calling a constructor, calling a static method, accessing a static property or accessing a static field. It also specifies whether the construction completely sets the value of the type to match your original instance; if not, you'll need to do additional reflection and setting of properties.
This is the mechanism that the WinForms Designer uses when generating the code-behind for the controls on the form, so it is supported for the common types that show up as properties of controls accessible from the designer.
The int type doesn't have any constructors.
The code you write is going to depend on the property's type; normally, you'd write special-case code for string, int and other primitive types.
Your code above contains the literal 0; how do you decide what values to pass to the constructors?

Categories

Resources