Heys, guys!
I'm trying to get values from my objects by reflection, buy I have a problem: I can get a value from the base object, but I can't get values from inner objects, like this:
public class Employee
{
public long EmployeeID { get; set; }
public string EmployeeNumber { get; set; }
public DateTime EmployeeAdmissionDate { get; set; }
public Person EmployeePerson { get; set; }
}
public class Person
{
public long PersonID { get; set; }
public string PersonName { get; set; }
public DateTime PersonBirthday { get; set; }
}
private static void GetDTOProperties(IDictionary<string, object> dicProperties, Type objectToSerialize)
{
Type typeOfObject = objectToSerialize is Type ? objectToSerialize as Type : objectToSerialize.GetType();
PropertyInfo[] properties = typeOfObject.GetProperties();
foreach (PropertyInfo property in properties)
{
if (!property.PropertyType.IsClass || property.PropertyType.Equals(typeof(string)))
dicProperties.Add(string.Format("{0}_{1}", property.DeclaringType.Name.ToLower(), property.Name.ToLower()), property.GetValue(typeOfObject, null));
else
GetDTOProperties(dicProperties, property.PropertyType);
}
}
public static void Main(string[] args)
{
Employee objEmployee = new Employee();
objEmployee.EmployeeID = 1;
objEmployee.EmployeeNumber = 457435;
objEmployee.EmployeeAdmissionDate = DateTime.Now;
objEmployee.EmployeePerson = new EmployeePerson();
objEmployee.EmployeePerson.PersonID = 123;
objEmployee.EmployeePerson.PersonName = "Kiwanax";
objEmployee.EmployeePerson.PersonBirthday = DateTime.Now;
IDictionary<string, object> dicProperties= new Dictionary<string, object>();
GetDTOProperties(dicPropriedades, objEntidadeAluno.GetType());
foreach (string m in dicProperties.Keys)
Console.WriteLine(m + " - " + dicProperties[m]);
Console.ReadLine();
}
The base values I can get, but the values of "Person" inner object I can't. Anyone has idea? Thanks!!
You can update your method like this:
private static void GetDTOProperties(IDictionary<string, object> dicProperties, object objectToSerialize)
{
Type typeOfObject = objectToSerialize is Type ? objectToSerialize as Type : objectToSerialize.GetType();
PropertyInfo[] properties = typeOfObject.GetProperties();
foreach (PropertyInfo property in properties)
{
object val = objectToSerialize is Type ? property.PropertyType : property.GetValue(objectToSerialize, null);
if (!property.PropertyType.IsClass || property.PropertyType.Equals(typeof(string)))
{
dicProperties.Add(string.Format("{0}_{1}", property.DeclaringType.Name.ToLower(), property.Name.ToLower()), val);
}
else
GetDTOProperties(dicProperties, val);
}
}
So there wont ba any problems with objects and you can send actual objects to that method. If you send type of object, than you will get Types as Values in dictionary
Related
I'm creating a component for Blazor and facing an issue when I have to create at runtime an object and I want to copy most of the properties, basically to clone an object but without same properties.
For example, the class Checkbox has a property called Choices.
public class Checkbox : ElementBase, IElement {
public virtual List<object>? Choices { get; set; }
}
This property stores string or CheckboxChoice.
public class CheckboxChoice
{
public string? Label { get; set; }
public string? VisibleIf { get; set; }
}
So, at runtime I have to create a new instance of an object (for example Checkbox) to display the component I want. To create an instance, I use this code (el is an implementation of IElement)
var newInstance = el.GetType();
var instance = Activator.CreateInstance(newInstance) as IElement;
Now, I have to copy some of the property's values from el to the instance. In order to copy all the properties I want, I use this extension
public static class ElementExtensions
{
public static T CopyTo<T, S>(this T target, S source, string[] propertyNames)
{
if (source == null)
return target;
Type sourceType = typeof(S);
Type targetType = typeof(T);
BindingFlags flags = BindingFlags.IgnoreCase | BindingFlags.Public |
BindingFlags.Instance;
PropertyInfo[] properties = sourceType.GetProperties();
foreach (PropertyInfo sPI in properties)
{
if (!propertyNames.Contains(sPI.Name))
{
PropertyInfo tPI = targetType.GetProperty(sPI.Name, flags);
if (tPI != null && tPI.CanWrite &&
tPI.PropertyType.IsAssignableFrom(sPI.PropertyType))
{
tPI.SetValue(target, sPI.GetValue(source, null), null);
}
}
}
return target;
}
}
The problem with this extension is that it is not copying the property Choices or other property that are not a primitive type.
I don't want to use AutoMapper because I like to have a light component without dependencies, apart from .NET6. How can I change the function to copy also the complex properties?
Update
This is the code I use. ElementData has the list of components (like Checkbox and Textbox) to display.
public List<IElement>? RepeaterElements { get; set; }
foreach (var el in ElementData)
{
var newInstance = el.GetType();
var instance = Activator.CreateInstance(newElement, null);
instance.CopyTo(data, new[] { "Parent", "Index", "QuestionNumber", "Name" });
instance.Parent = parentName;
instance.Index = row;
instance.QuestionNumber = question;
instance.Name = instance.GetElementName();
RepeaterElements.Add(new RepeaterElement() { Element = instance, Row = row });
}
I've always defaulted to using serialization to handle deep object copies.
Here is an example:
using static Newtonsoft.Json.JsonConvert;
var instanceOfMyClass = new MyClass()
{
Id = 1,
Name = "John Doe",
Age = 21
};
Console.WriteLine(SerializeObject(instanceOfMyClass));
var copyOfMyClass = DeserializeObject<MyClass>(SerializeObject(new { instanceOfMyClass.Name, instanceOfMyClass.Age }));
Console.WriteLine(SerializeObject(copyOfMyClass));
public class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
It's a simple and performant pattern on small classes.
EDIT:
Can also be done between classes if the Property names are the same:
sing static Newtonsoft.Json.JsonConvert;
var instanceOfMyClass = new MyClass()
{
Id = 1,
Name = "John Doe",
Age = 21
};
Console.WriteLine(SerializeObject(instanceOfMyClass));
var copyOfMyClass = DeserializeObject<MySecondClass>(SerializeObject(new { instanceOfMyClass.Name, instanceOfMyClass.Age }));
Console.WriteLine(SerializeObject(copyOfMyClass));
public class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class MySecondClass
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
I have no problems with properties that are non-nullable collections but everything changes when the property is a nullable collection...
Given a class with nullable collection properties (int?[], long?[], List<Nullable>, ...)
public class SomeClass
{
public int?[] PropertyX { get; set; }
public long?[] PropertyY { get; set; }
public float?[] PropertyZ { get; set; }
public List<decimal?> PropertyList { get; set; }
public string Name { get; set; }
}
How can I tell if the property is a nullable collection?
Does the nullable collection have any actual values?
Lastly what is the nullable collections data type?
public class SomeOtherClass
{
public string Discover(SomeClass someClass)
{
var sb = new StringBuilder();
foreach (var propertyInfo in someClass.GetType().GetProperties())
{
var propValue = propertyInfo.GetValue(someClass, new object[] { });
if (propValue.IsNullableCollection() && propValue.HasValue())
{
sb.AppendFormat("{0} data type is {1}", propValue.GetDataType());
}
}
return sb.ToString();
}
}
Example:
void Main
{
var sc = new SomeClass()
{
PropertyX = new int?[2]{50,10}
};
var output = new SomeOtherClass().Discover(sc);
//Display Output ...
}
"PropertyX has values and data type is int."
I've written a method that should give you all the tools you need for your task:
public static void Discover(Type mainType)
{
foreach (var pi in mainType.GetProperties())
{
var t = pi.PropertyType;
if (t.IsArray)
{
(var isNullable, var innerT) = ExamineForNullable(t.GetElementType());
Console.WriteLine($"{pi.Name} data type is an array of {innerT} (nullable: {isNullable})");
}
else if (t.GetInterface(nameof(ICollection)) != null)
{
//TODO this is true for dictionaries too!
if (t.IsGenericType)
{
(var isNullable, var innerT) = ExamineForNullable(t.GetGenericArguments()[0]);
Console.WriteLine($"{pi.Name} data type is collection of {innerT} (nullable: {isNullable})");
}
else
{
//TODO
}
}
else
{
Console.WriteLine($"{pi.Name} data type is {t}");
}
}
}
static (bool nullable, Type type) ExamineForNullable(Type t)
=> t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)
? (true, Nullable.GetUnderlyingType(t))
: (false, t);
Let's run it with the following class, which is a modified version of you example class:
public class SomeClass
{
public int?[] PropertyX { get; set; }
public List<decimal?> List1 { get; set; }
public List<decimal> List2 { get; set; }
public string Name { get; set; }
public Dictionary<string, decimal?> Dictionary { get; set; }
}
(...)
Discover(typeof(SomeClass));
Please not that you don't need an object of the class to examine the types: Discover( new SomeClass()) vs Discover(typeof(SomeClass).
The output is:
PropertyX data type is an array of System.Int32 (nullable: True)
List1 data type is collection of System.Decimal (nullable: True)
List2 data type is collection of System.Decimal (nullable: False)
Name data type is System.String
Dictionary data type is collection of System.String (nullable: False)
I am working on an angular project with Web API where I need to map an entity TblEmployee from form posted values.
I have created a static function ConvertValue to dynamic typecasting values, but on calling this function I am not able to pass exact parameters.
Below is my code and I am getting this error on compile time:
'propertyInfo' is a var but is used like a type.
public partial class TblEmployee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
public string City { get; set; }
}
public int Create()
{
TblEmployee employee = new TblEmployee();
Dictionary<String, Object> _formvalue = new Dictionary<String, Object>();
foreach (string key in HttpContext.Request.Form.Keys)
{
string val = HttpContext.Request.Form[key];
var propertyInfo = typeof(TblEmployee).GetProperty(key);
if (propertyInfo != null)
{
var myVal = Filters.ConvertValue<propertyInfo.PropertyType.Name>(val);
propertyInfo.SetValue(employee, myVal);
}
}
return objemployee.AddEmployee(employee);
}
public static T ConvertValue<T>(string value)
{
return (T)Convert.ChangeType(value, typeof(T));
}
I want to create some classes, that have to look a certain way ( the way shown in my example).
Some of the class properties are classes (or structs) themselves.
I want to write a method within my classes that get the Property-Values of all the Properties, that are Structs and write them to a string.
So this is what my classes look like:
public class car
{
public string brand { get; set; }
public tire _id { get; set; }
public string GetAttributes()
{
Type type = this.GetType();
PropertyInfo[] properties = type.GetProperties();
foreach(PropertyInfo propertyInfo in properties)
if (propertyInfo.PropertyType.ToString().Contains("_"))
{
//I want to write the actual value of the property here!
string nested_property_value = ...
return nested_property_value;
}
}
}
this is what my structs look like:
public struct tire
{
public int id { get; set; }
}
this would be the Main Program:
tire mynewtire = new tire()
{
id = 5
};
car mynewcar = new car()
{
_id = mynewtire
};
Anyone has an idea how to create the GetAttributes-Methode? I've been trying to figure this out for ages now, but don't get there...
This code will get you started. I recommend you look at other serialisation methods (such as JSON) as well.
using System;
namespace Test
{
public class car
{
public string brand { get; set; }
public tire _id { get; set; }
public string GetAttributes()
{
var type = this.GetType();
var returnValue = "";
var properties = type.GetProperties();
foreach (var propertyInfo in properties)
{
// Look at properties of the car
if (propertyInfo.Name.Contains("_") && propertyInfo.PropertyType.IsValueType &&
!propertyInfo.PropertyType.IsPrimitive)
{
var propValue = propertyInfo.GetValue(this);
var propType = propValue.GetType();
var propProperties = propType.GetProperties();
foreach (var propPropertyInfo in propProperties)
{
// Now get the properties of tire
// Here I just concatenate to a string - you can tweak this
returnValue += propPropertyInfo.GetValue(propValue).ToString();
}
}
}
return returnValue;
}
}
public struct tire
{
public int id { get; set; }
}
public class Program
{
static void Main(string[] args)
{
var mynewtire = new tire()
{
id = 5
};
var mynewcar = new car()
{
_id = mynewtire
};
Console.WriteLine(mynewcar.GetAttributes());
Console.ReadLine();
}
}
}
So I am using reflection to loop through the properties of one object and populating the values on a different object with properties of the same name. This works great but the problem comes when the property type is a collection. I want to be able to loop through each of the objects in the source collection and populate the same list with objects in the source collection.
public class SourceMessage
{
public string Name { get; set; }
public int Version { get; set; }
public IList<ValueDefinition> Values { get; set; }
}
public class ValueDefinition
{
public string Name { get; set; }
public string Value { get; set; }
}
public class TargetObject
{
public TargetObject()
{
Values = new List<TargetValueDefinition>();
}
public string Name { get; set; }
public int Version { get; set; }
public IList<TargetValueDefinition> Values { get; set; }
}
public class TargetValueDefinition
{
public string Name { get; set; }
public string Value { get; set; }
}
Then I use Reflection to populate the target from the source.
public static void PopulateFromMessage<T, TS>(ref T targetEntity, TS message)
{
var sourceType = typeof(TS);
var targetType = typeof(T);
foreach (var targetPropInfo in targetType.GetProperties())
{
if (sourceType.GetProperty(targetPropInfo.Name) != null)
{
var obj = sourceType.GetProperty(targetPropInfo.Name);
if (obj.PropertyType.Namespace == "System.Collections.Generic")
{
//var x = targetType.GetProperty(targetPropInfo.Name);
//PopulateFromMessage(ref x, sourceType.GetProperty(targetPropInfo.Name));
continue;
}
targetPropInfo.SetValue(targetEntity, sourceType.GetProperty(targetPropInfo.Name).GetValue(message), null);
}
}
}
So calling this would be like this:
private void DenormalizeMessage(SourceMessage message)
{
var newTargetObject = new TargetObject();
PopulateFromMessage(ref newTargetObject , message);
}
I can identify when the property is a collection but am uncertain of how to create new TargetValueDefinitions and populate them with the values from ValueDefinitions. In the end it is pretty much a copy of the SourceMessage in the form of a TargetObject.
This all stems from receiving messages and transforming them into objects with the same property names.
If your problem is iterating through items contained inside a single property when it is a collection, then the key would be to read the property value into a dynamic variable and not an object variable that is by default, this way you could use a foreach for it.
dynamic propVal = inputProperty.GetValue(item);
foreach (var subItem in propVal)
{
//do your stuff
}
Disclaimer: This is extremely unsafe to do and makes a lot of assumptions but it should puth you on the right path.
Change you method to this:
public static void PopulateFromMessage<T, TS>(T targetEntity, TS message)
{
var sourceType = typeof (TS);
var targetType = typeof (T);
foreach (var targetPropInfo in targetType.GetProperties())
{
if (targetPropInfo.PropertyType.IsGenericType)
{
if (targetPropInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))
{
var originalList = sourceType.GetProperty(targetPropInfo.Name).GetValue(message) as IList;
if (originalList != null)
{
var argumentType = targetPropInfo.PropertyType.GetGenericArguments();
var listType = typeof (List<>);
var concreteType = listType.MakeGenericType(argumentType);
var newList = Activator.CreateInstance(concreteType) as IList;
foreach (var original in originalList)
{
var targetValue = Activator.CreateInstance(argumentType[0]);
// do this yourself. Here we're converting ValueDefinition to TargetValueDefinition
// targetValue.Fill(original);
}
targetPropInfo.SetValue(targetEntity, newList);
}
}
}
else
{
if (sourceType.GetProperty(targetPropInfo.Name) != null)
{
var obj = sourceType.GetProperty(targetPropInfo.Name);
if (obj.PropertyType.Namespace == "System.Collections.Generic")
{
//var x = targetType.GetProperty(targetPropInfo.Name);
//PopulateFromMessage(ref x, sourceType.GetProperty(targetPropInfo.Name));
continue;
}
targetPropInfo.SetValue(targetEntity, sourceType.GetProperty(targetPropInfo.Name).GetValue(message), null);
}
}
}
}
You should create a interface for each class (implement the methods and properties on interface) and implement it in each class. After, in function PopulateFromMessage should specify the interface allowed in method, with this you can use directly the properties of class with T and TS generic types.