Recursion on nested Generic classes - c#

I have an issue.
Say I have a Generic class which can have generic properties of other classes and can even have a list of other classes.
If i have a function like
public void Read<T>() where T: class, new()
{
// Create an instance of our generic class
var model = new T();
var properties = typeof(T).GetProperties();
// Loop through the objects properties
for(var property in properties) {
// Set our value
SetPropertyValue(property, model, "test");
}
}
private void SetPropertyValue(PropertyInfo property, object model, string value) {
// Set our property value
property.SetValue(model, value, null);
}
that would work if I had a class like this:
public class Person
{
public string Name { get; set; }
}
and I invoked the Read method like this:
Read<Person>();
But if my Person model was like this:
public class Person
{
public string Name { get; set; }
public Company Company { get; set; }
}
public class Company
{
public string Name { get; set; }
}
And I tried to invoke the Read method again, it would fail because of the property have it's own list of properties.
What would be better is if it traversed them too. Is there a way to do that?

This answer can help.
You should end with something like this:
if (t.IsPrimitive || t == typeof(Decimal) || t == typeof(String) || ... )
SetPropertyValue(property, model, "test");
else
// You will have to recode this line,
// it's just to show you the idea how you can work around
SetPropertyValue(property, model, Read.MakeGeneric(property.Type)());
You will also need to return your model variable from your Read method.
The condition is depending on what type you want to overwrite, if it's like on your example, you can change the condition to match only strings and add a check on the else to check property that are objects.

You can set the property value directly if it is a string, otherwise you can return value of a method similar to Read that takes Type as a parameter to create a model and fill its properties recursively.
public void Read<T>() where T : class, new()
{
// Create an instance of our generic class
var model = new T();
var properties = typeof(T).GetProperties();
// Loop through the objects properties
foreach(var property in properties)
{
// Set our value
SetPropertyValue(property, model, "test");
}
}
private void SetPropertyValue(PropertyInfo property, object model, string value)
{
if (property.PropertyType == typeof(string))
{
// Set our property value
property.SetValue(model, value, null);
}
else
{
var submodel = Read(property.PropertyType);
property.SetValue(model, submodel, null);
}
}
private object Read(Type type)
{
if (!IsTypeSupported(type))
{
throw new ArgumentException();
}
var model = type.GetConstructor(new Type[0]).Invoke(new object[0]);
var properties = type.GetProperties();
foreach (var property in properties)
{
SetPropertyValue(property, model, "test");
}
return model;
}
private bool IsTypeSupported(Type type)
{
return type.IsClass && type.GetConstructor(new Type[0]) != null;
}

Related

Using parameter instead of a model definiton

Component is a model,and Storage is one of its definitons.Is there a way to use a parameter instead of Storage?
public IActionResult Filtered(string parameter)
{
return View(viewModel.Where(x => x.Component.Storage != "").ToList());
}
I am assuming that parameter is of type string. This is just a sample code. You can customize it to your needs.
var res = from m in viewModel // I don't know what is inside this viewModel
where !String.IsNullOrEmpty(parameter)
select m;
return View(res);
You can use reflection to get value by parameter like this
var component = new Component { Storage = "A1" };
var valueOfName = component["Name"];
Console.WriteLine(valueOfName);
public partial class Component
{
public string Storage { get; set; }
public object this[string propertyName]
{
get
{
var type = GetType();
var property = type.GetProperty(propertyName);
if (property == null) throw new Exception("Class donesn't have this property");
var value = property.GetValue(this, null);
return value;
}
private set{};
}
}
If you can modify the class you don't need the partial key word on Component class
Updated
To get the parameter from the url add it to the function
public IActionResult Filtered(string propertyName)
{
var prop = typeof(Component).GetProperty(propertyName)
return View(viewModel.Where(x => !Equals(prop.GetValue(x.Component), "")).ToList());
}

Mapping similar objects using reflection: Object does not match target type

I am at a complete loss here, despite looking at multiple SO posts and anything else I can think of.
My goal here is to make a really, really simple mapper. Something I can basically use as a tool in some unit tests. It doesn't need to be sophisticated or anything -- just map high-level primitive and string values of one object to another. So the basic algorithm is:
Get all properties from TFrom
Get all properties from TTo
Get all properties that are in both, matched by name.
I know this could be a bug in that they could have the same name but a different type, but let's set that aside. It's not what I'm running into here -- the properties and types match between classes.
Create an instance of TTo that we can copy to.
For each property that was mapped between the objects:
Get the value off of the from object
Convert the value to the type of the property
Set the value on the to object
The problem is that no matter what I do, and no matter what the type of the property is (int or string, for example) I get the following:
Object does not match the target type.
Here is the code I'm using:
public TTo Map<TFrom, TTo>(TFrom from)
{
if (from == null) return default;
var fromProps = GetProperties(typeof(TFrom));
var toProps = GetProperties(typeof(TTo));
// Props that can be mapped from one to the other
var propsToCopy = fromProps.Intersect(toProps, new PropertyComparer()).ToList();
var returnObject = (TTo)Activator.CreateInstance(typeof(TTo));
foreach (var prop in propsToCopy)
{
// Copy the values
var fromValue = prop.GetValue(from, null);
var convertedValue = Convert.ChangeType(fromValue, prop.PropertyType);
prop.SetValue(returnObject, convertedValue, null);
}
return returnObject;
}
public PropertyInfo[] GetProperties(Type objectType)
{
var allProps = objectType.GetProperties(
BindingFlags.Public | BindingFlags.Instance);
return allProps.Where(p => p.PropertyType.IsPrimitive ||
p.PropertyType == typeof(string)).ToArray();
}
private class PropertyComparer : IEqualityComparer<PropertyInfo>
{
public bool Equals(PropertyInfo x, PropertyInfo y)
{
return x.Name.Equals(y.Name);
}
public int GetHashCode(PropertyInfo obj)
{
return obj.Name.GetHashCode();
}
}
And here's an example of a way I would call it, with sample classes:
public class Foo
{
public string StringProp { get; set; }
public int IntProp { get; set; }
}
public class FooOther
{
public string StringProp { get; set; }
public int IntProp { get; set; }
}
var foo = new Foo { IntProp = 1, StringProp = "foo" };
var mappedFoo = Map<Foo, FooOther>(foo);
About the only hint I've gotten out of Visual Studio is from the watch window: if the property type is a string, the watch window reports the type of convertedValue as object. If the property type is an int, the watch window reports object {int}.
The PropertyInfo you are using is still coupled to the type the property it is representing is a member of, so you aren't able to use it to set the value of an object of another type without the error you are getting.
Here's a shortened example of the behavior:
public class A {
public string Id {get;set;}
}
public class B {
public string Id {get;set;}
}
void Main()
{
var test = new A() { Id = "Test"};
var prop = test.GetType().GetProperty("Id");
var b = (B)Activator.CreateInstance(typeof(B));
var fromValue = prop.GetValue(test);
var converted = Convert.ChangeType(fromValue, prop.PropertyType);
prop.SetValue(b, converted, null); // Exception
}
This makes sense if you think of the PropertyInfo as a member of A. To fix this, you'll want to get a property info that's specific to your type. I can fix up my example with the following:
var propTo = typeof(B).GetProperty(prop.Name);
propTo.SetValue(b, converted, null);
Console.WriteLine(b.Id); // Output: Test
Bringing that together, if you change the contents of your foreach to the following you should be in the clear:
foreach (var prop in propsToCopy)
{
// Copy the values
var fromValue = prop.GetValue(from, null);
var convertedValue = Convert.ChangeType(fromValue, prop.PropertyType);
var propTo = typeof(TTO).GetProperty(prop.Name);
propTo.SetValue(returnObject, convertedValue, null);
}

Create instance of property's unknown class type at runtime

I need to detect when a property's type is a class type and its constructors arguments if any. The classes will vary and cannot be hard coded. I've got a property type like below and using reflection I'm handling the properties differently depending upon determined type.
public class SomeClass
{
// Process this one and instantiate its class type
public WhateverClass Whatever
{
get;
set;
}
// This one will be skipped since its not a user defined class type
public string SomePropName
{
get;
set;
}
}
Now when I reflect the properties in the class (SomeClass for ex.) I need to do something different with the property types but there are some types that are classes and they may or may not have arguments needed in the constructors but since its all determined at run time I have to reflect the constructor dynamically.
if (propertyInfo.PropertyType.IsClass)
{
var propType = propertyInfo.PropertyType.UnderlyingSystemType;
// Something like this
var ctx = propType.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, null, null);
// todo: instanciate new class instance
}
Now is there a better way to skin this cat and if not how can I create the class when I wont know the constructor arguments?
Thanks
If you need to know any/all constructors for an object, there is a "GetConstructors" method that returns an array of constructors for the class:
propType.GetConstructors();
Ex:
classes
public class PropertyClass
{
public PropertyClass()
{
Value = "default";
}
public PropertyClass(string value)
{
Value = value;
}
public string Value { get; private set; }
}
public class WhateverClass
{
public PropertyClass Property { get; set; }
}
...
usage
var propertyInstances = GeneratePropertyForEachConstructor(typeof(WhateverClass), "Property").Cast<PropertyClass>();
foreach (PropertyClass propertyInstance in propertyInstances)
{
Console.WriteLine("value: {0}", propertyInstance.Value);
}
...
methods:
public List<object> GeneratePropertyForEachConstructor(Type type, string propertyName)
{
var propertyInfo = type.GetProperty(propertyName);
return GeneratePropertyForEachConstructor(type, propertyInfo);
}
public List<object> GeneratePropertyForEachConstructor(Type type, PropertyInfo propertyInfo)
{
List<object> results = new List<object>();
if (propertyInfo.PropertyType.IsClass)
{
var propType = propertyInfo.PropertyType.UnderlyingSystemType;
var constructors = propType.GetConstructors();
foreach (ConstructorInfo constructorInfo in constructors)
{
var parameterInfo = constructorInfo.GetParameters();
var constructorParams = new List<object>();
foreach (ParameterInfo constructorParam in parameterInfo)
{
object value = constructorParam.HasDefaultValue
? constructorParam.DefaultValue
: constructorParam.ParameterType.IsValueType
? Activator.CreateInstance(constructorParam.ParameterType)
: null;
constructorParams.Add(value);
}
results.Add(constructorInfo.Invoke(constructorParams.ToArray()));
}
}
return results;
}
output
value: default
value:
in this case we can see that it looped through both of the constructors and passed in the default value for string for the constructor that has a string parameter.

How to manipulate the properties of a user-defined class on runtime?

I have a huge user-defined class with lots of properties and some of them has to be set with a certain value.
To be more specific, all the public properties with the type of string in this user-defined class have to be "emptied" at the end of execution.
So i reached all the public properties one by one (like 300 properties) and assigned them with my 300 lines of code. What i did to solve this problem, did what i needed, but didn't satisfy me of course.
So i decided to write a Helper Method (as Extension Method) in C# that iterates through the properties of an object instance and access / manipulate them dynamically. I've seen some similar questions about iterating through properties in here, but didn't see anything about changing property values so far. Here is what i tried:
public static void SetDefaultStringValues(this Object myObject)
{
PropertyInfo[] aryProperties = entityObject.GetType().GetProperties();
foreach (object property in aryProperties)
{
if (property is String)
{
//property = String.Empty;
}
}
}
The commented part had to be the line that i set the variables but that wasn't really a success story :) I'll appreciate any help.
Use PropertyInfo.SetValue method, more on this in here
You can use PropertyDescriptor of System.ComponentModel for lazy coding. Assuming you want to iterate over public instance methods (not statics) you can try the following sample:
Test class:
public class TestClass
{
private int mIntMemeber = 0; // # to test int type
private string mStringMember = "abc"; // # to test string type (initialized)
private string mNullStringMember = null; // # to test string type (null)
private static string mStaticNullStringMember; // # to test string type (static)
// # Defining properties for each member
public int IntMember
{
get { return mIntMemeber; }
set { mIntMemeber = value; }
}
public string StringMember
{
get { return mStringMember; }
set { mStringMember = value; }
}
public string NullStringMember
{
get { return mNullStringMember; }
set { mNullStringMember = value; }
}
public static string StaticNullStringMember
{
get { return mStaticNullStringMember; }
set { mStaticNullStringMember = value; }
}
}
SetDefaultStringValues() Extension Method:
public static string SetDefaultStringValues(this TestClass testClass)
{
StringBuilder returnLogBuilder = new StringBuilder();
// # Get all properties of testClass instance
PropertyDescriptorCollection propDescCollection = TypeDescriptor.GetProperties(testClass);
// # Iterate over the property collection
foreach (PropertyDescriptor property in propDescCollection)
{
string name = property.Name;
Type t = property.PropertyType; // # Get property type
object value = property.GetValue(testClass); // # Get value of the property (value that member variable holds)
if (t == typeof(string)) // # If type of propery is string and value is null
{
property.SetValue(testClass, String.Empty); // # Set value of the property (set member variable)
value = String.Empty; // # <-- To prevent NullReferenceException when printing out
}
returnLogBuilder.AppendLine("*****\nName:\t{0}\nType:\t{1}\nValue:\t{2}", name, t.ToString(), value.ToString());
}
returnLogBuilder.AppendLine("*****");
return returnLogBuilder.toString();
}
You could also check if value is null within the loop. SetDefaultStringValues method parameter could be any object instance. You can change it to SetDefaultStringValues(this object o) and use it as extension method for your defined class instance.
You need a different syntax in order to check the property type; furthermore, you shold check that the property has a public setter.
if (property.PropertyType == typeof(string) && property.GetSetMethod() != null)
property.SetValue(entityObject, "");
foreach (PropertyInfo property in aryProperties)
{
if (property.PropertyType == typeof(string) && property.CanWrite)
{
property.SetValue(myObject, "", null);
}
}

Using reflection to get values from properties from a list of a class

I am trying to get the values from objects inside a list which is part of a main object.
I have the main object which contains various properties which can be collections.
Right now I am trying to figure out how to access a generic list which is contained in the object.
///<summary>
///Code for the inner class
///</summary>
public class TheClass
{
public TheClass();
string TheValue { get; set; }
} //Note this class is used for serialization so it won't compile as-is
///<summary>
///Code for the main class
///</summary>
public class MainClass
{
public MainClass();
public List<TheClass> TheList { get; set; }
public string SomeOtherProperty { get; set; }
public Class SomeOtherClass { get; set }
}
public List<MainClass> CompareTheValue(List<object> MyObjects, string ValueToCompare)
{
//I have the object deserialised as a list
var ObjectsToReturn = new List<MainClass>();
foreach(var mObject in MyObjects)
{
//Gets the properties
PropertyInfo piTheList = mObject.GetType().GetProperty("TheList");
object oTheList = piTheList.GetValue(MyObject, null);
//Now that I have the list object I extract the inner class
//and get the value of the property I want
PropertyInfo piTheValue = oTheList.PropertyType
.GetGenericArguments()[0]
.GetProperty("TheValue");
//get the TheValue out of the TheList and compare it for equality with
//ValueToCompare
//if it matches then add to a list to be returned
//Eventually I will write a Linq query to go through the list to do the comparison.
ObjectsToReturn.Add(objectsToReturn);
}
return ObjectsToReturn;
}
I've tried to use a SetValue() with MyObject on this, but it errors out with (paraphrased):
object is not of type
private bool isCollection(PropertyInfo p)
{
try
{
var t = p.PropertyType.GetGenericTypeDefinition();
return typeof(Collection<>).IsAssignableFrom(t) ||
typeof(Collection).IsAssignableFrom(t);
}
catch
{
return false;
}
}
}
To Get/Set using reflection you need an instance. To loop through the items in the list try this:
PropertyInfo piTheList = MyObject.GetType().GetProperty("TheList"); //Gets the properties
IList oTheList = piTheList.GetValue(MyObject, null) as IList;
//Now that I have the list object I extract the inner class and get the value of the property I want
PropertyInfo piTheValue = piTheList.PropertyType.GetGenericArguments()[0].GetProperty("TheValue");
foreach (var listItem in oTheList)
{
object theValue = piTheValue.GetValue(listItem, null);
piTheValue.SetValue(listItem,"new",null); // <-- set to an appropriate value
}
See if something like this helps you in the right direction: I got the same error a while back and this code snipped solved my issue.
PropertyInfo[] properties = MyClass.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.Name == "MyProperty")
{
object value = results.GetType().GetProperty(property.Name).GetValue(MyClass, null);
if (value != null)
{
//assign the value
}
}
}

Categories

Resources