.Net Get Property Values of Object by Key, to any depth - c#

I would like to be able to access the value of an object property to any depth having only the string-key of the property. Also, if possible, using collection indexing on List properties.
So, If I have the string "Person.Surname" then I could get the value "Smith" from and instanciated CaseConductor object. So given some setup code like this ...
//- Load a caseConductor
var caseConductor = new CaseConductor();
caseConductor.CaseID = "A00001";
// person
caseConductor.Person = new Person();
caseConductor.Person.Surname = "Smith" ;
caseConductor.Person.DOB = DateTime.Now ;
// case note list
caseConductor.CaseNoteList = new List<Note>();
caseConductor.CaseNoteList.Add(new Note { NoteText = "A-1" , NoteDt = DateTime.Now });
caseConductor.CaseNoteList.Add(new Note { NoteText = "B-2", NoteDt = DateTime.Now });
// I could do this ...
object val = caseConductor.SomeCleverFunction("Person.Surname");
// or this ...
object val = caseConductor.SomeCleverFunction("CaseNoteList[0].NoteText");
Has anyone done this before ?
Here are some setup classes ...
class Note
{
public Guid NoteID { get; set; }
public string NoteText { get; set; }
public DateTime? NoteDt { get; set; }
}
public class Person
{
public Guid PersonID { get; set; }
public string Surname { get; set; }
public string Forename { get; set; }
public DateTime? DOB { get; set; }
}
class CaseConductor
{
public String CaseID{get;set;}
public Person Person { get; set; }
public List<Note> CaseNoteList { get; set; }
}
Our use case is to iterate over a series of appropriately named content controls in a word dcoument template using open xml sdk 2, and poke values into a newly created word documents, something like this ...
List<SdtElement> ccList = wordprocessingDocument.MainDocumentPart.Document.Descendants<SdtElement>().ToList();
foreach (var cc in ccList)
{
string alias = cc.SdtProperties.GetFirstChild<SdtAlias>().Val.Value;
switch (cc.GetType().Name)
{
case "SdtRun":
SdtRun thisRun = (SdtRun)cc;
//thisRun.Descendants<Text>().First().Text = theValueToBePoked ;
break;
}
}

Use good old reflection. I have tested and this actually works:
public static object GetValue(object o, string propertyName)
{
Type type = o.GetType();
PropertyInfo propertyInfo = type.GetProperties(BindingFlags.Public | BindingFlags.Instance ).Where(x => x.Name == propertyName).FirstOrDefault();
if(propertyInfo!=null)
{
return propertyInfo.GetValue(o, BindingFlags.Instance, null, null, null);
}
else
{
return null; // or throw exception
}
}

I'm assuming that
caseConductor.SomeCleverFunction
is not a static method, and has access to the Person object, and that the Person object itself if a public property.
I'm also assuming that you want to pass a string like "prop.address.street" where each sub property is an class that containts a puplic property with that name
Split the string input on the period, find the left most string
Use reflection to get a list of properties ( typeof(caseconductor).GetProperties() )
Find the matching property, call GetValue on it, passing the last known solid object (starting with 'this') and storing a refernce to it.
if there is more sub properties in the string left, repeat to step 1, removing the left most part of the string.
otherwise, call GetValue() on the property, using the last GetValue() return object from step 3, and return it.
Something like:
"prop.address.street" -> find property "prop" from 'this' and GetValue,
there is still more "."'s so repeat, storing return value
"address.street" -> find property "address" from the last returned GetValue, and get it's value.
there is still more "."'s so repeat, storing return value
"street" -> find property "street" from the last returned GetValue, and return it's value.
End of string, return last value
Edit -
This is pretty rough, but toss it into LinqPAD and take a look.
http://www.linqpad.net/
Edit #2 - you should be able to index into arrays using the ^ syntax below.
Again this is reaaaaaaaaally rough, just enough to get a working example.
Edit #3 - Cleaned up the example slightly and changed it from my example classes to yours.
void Main()
{
//- Load a caseConductor
var caseConductor = new CaseConductor();
caseConductor.CaseID = "A00001";
// person
caseConductor.Person = new Person();
caseConductor.Person.Surname = "Smith" ;
caseConductor.Person.DOB = DateTime.Now ;
// case note list
caseConductor.CaseNoteList = new List<Note>();
caseConductor.CaseNoteList.Add(new Note { NoteText = "A-1" , NoteDt = DateTime.Now });
caseConductor.CaseNoteList.Add(new Note { NoteText = "B-2", NoteDt = DateTime.Now });
// I could do this ...
string val1 = caseConductor.GetPropertyValue<string>("Person.Surname");
// or this ...
Note val2 = caseConductor.GetPropertyValue<Note>("CaseNoteList^1");
val1.Dump("val1"); //this is a string
val2.Dump("val2"); //this is a Note
}
public static class extensions
{
public static T GetPropertyValue<T>(this object o,string Properties) where T:class
{
var properties = Properties.Split('.');
var indexsplit = properties[0].Split('^');
var current = indexsplit[0];
var prop = (from p in o.GetType().GetProperties() where p.Name == current select p).Take(1).Single();
var val = prop.GetValue(o,null);
if(indexsplit.Length>1)
{
var index = int.Parse(indexsplit[1]);
IList ival = (IList)val;
val = ival[index];
}
if(properties[0] == Properties)
return (T)val;
else
return val.GetPropertyValue<T>(Properties.Replace(properties[0]+".",""));
}
}
class Note
{
public Guid NoteID { get; set; }
public string NoteText { get; set; }
public DateTime? NoteDt { get; set; }
}
public class Person
{
public Guid PersonID { get; set; }
public string Surname { get; set; }
public string Forename { get; set; }
public DateTime? DOB { get; set; }
}
class CaseConductor
{
public String CaseID{get;set;}
public Person Person { get; set; }
public List<Note> CaseNoteList { get; set; }
}

OK, I came up with something which continues Aliosted and asowyer start suggestions, here it is. You can see I still having trouble with the index access of composed objects. Thnaks for your help.
#region object data ...
var model = new HcmlDocumentProductionModel();
model.CaseID = "A001";
model.CaseConductor = new CaseConductor();
model.CaseConductor.AField = "AField";
model.CaseConductor.Person = new Person();
model.CaseConductor.Person.Surname = "{Smith}";
model.CaseConductor.Person.DOB = DateTime.Now;
model.CaseConductor.CaseNoteList = new List<Note>();
model.CaseConductor.CaseNoteList.Add(new Note { NoteText = "A-1", NoteDt = DateTime.Now, NoteTypeEnum = NoteTypeEnum.CaseNote });
model.CaseConductor.CaseNoteList.Add(new Note { NoteText = "B-2", NoteDt = DateTime.Now, NoteTypeEnum = NoteTypeEnum.ReferralNote });
model.CaseConductor.CaseNoteList.Add(new Note { NoteText = "C-3", NoteDt = DateTime.Now, NoteTypeEnum = NoteTypeEnum.StatusNote });
model.CaseConductor.CaseNoteList.Add(new Note { NoteText = "d-3", NoteDt = DateTime.Now, NoteTypeEnum = NoteTypeEnum.CaseNote });
model.CaseConductor.CaseNoteList.Add(new Note { NoteText = "e-3", NoteDt = DateTime.Now, NoteTypeEnum = NoteTypeEnum.StatusNote });
model.CaseConductor.CaseNoteList.Add(new Note { NoteText = "f-3", NoteDt = DateTime.Now, NoteTypeEnum = NoteTypeEnum.CaseNote });
#endregion
string head = "";
string tail = "";
// tail
tail = "".Tail();
tail = "Surname".Tail();
tail = "Person.Surname".Tail();
tail = "CaseConductor.Person.Surname".Tail();
// head
head = "".Head();
head = "Surname".Head();
head = "Person.Surname".Head();
head = "CaseConductor.Person.Surname".Head();
// ObjectDictionary
//var person = new Person { Surname = "Smith" };
//var d = person.ObjectDictionary();
//object ovalue = d["Surname"];
// get value special
object o2 = model.CaseConductor.Person.ValueByKey("Surname");
object o3 = model.CaseConductor.Person.ValueByKey("DOB");
object o4 = model.CaseConductor.ValueByKey("Person.Surname");
object o5 = model.ValueByKey("CaseConductor.Person.Surname");
// get the list of ...
object o6 = model.ValueByKey("CaseConductor.CaseNoteList");
// get item - index thing does not work - get anull here
string noteText = model.CaseConductor.CaseNoteList[1].NoteText;
object o7 = model.ValueByKey("CaseConductor.CaseNoteList[1].NoteText");
namespace Zed
{
public static class Zed
{
public static object ValueByKey(this object o, string key)
{
if (!String.IsNullOrEmpty(key))
{
if (!key.Contains("."))
{
return (o.ObjectDictionary())[key];
}
else
{
// key contains a dot ; therefore get object by the name of the head
// and pass on that object and get propety by the tail
var d = o.ObjectDictionary();
var head = key.Head();
if (head.Contains("["))
{
string headMinusIndexer = head.Substring(0, head.IndexOf("["));
string indexString = head.Between("[", "]");
int index = Convert.ToInt32(indexString);
object oArray = d[headMinusIndexer];
//List<object> oList= d[headMinusIndexer];
// now get the object with the index, ... and continue
//object el = ((object[])oArray)[index];
return null;
}
else
{
var onext = d[head];
return onext.ValueByKey(key.Tail());
}
}
}
return null;
}
public static Dictionary<string,object> ObjectDictionary(this object o)
{
return o.GetType().GetProperties().ToDictionary(p => p.Name, p => p.GetValue(o, null));
}
public static string Head(this string key)
{
var head = String.Empty;
var splittBy = '.';
if (!String.IsNullOrEmpty(key))
{
var keyArray = key.Split(splittBy);
head = keyArray[0];
}
//-Return
return head;
}
public static string Tail(this string key)
{
var tail = "";
var splittBy = '.';
if (!String.IsNullOrEmpty(key))
{
var keyArray = key.Split(splittBy);
for (int i = 1; i < keyArray.Length; i++)
{
tail += (i > 1) ? "." + keyArray[i] : keyArray[i];
}
}
//-Return
return tail;
}
public static string Between(this string head, string start, string end)
{
string between = String.Empty ;
between = head.Substring(head.IndexOf(start) + 1, head.IndexOf(end) - head.IndexOf(start) - 1);
return between;
}
public static object ZGetValue( this object o, string propertyName)
{
Type type = o.GetType();
PropertyInfo propertyInfo = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name == propertyName).FirstOrDefault();
if (propertyInfo != null)
{
return propertyInfo.GetValue(o, BindingFlags.Instance, null, null, null);
}
else
{
return null;
}
}
}
}

Related

Check whether any property within class is null

I'm writing a simple console weather app (OpenWeatherMap) and I would like to validate whether response from OWM contains any null properties. This is my class containing properties:
public class WeatherMain
{
public Coord coord { get; set; }
public List<Weather> weather { get; set; }
public Main main { get; set; }
public Wind wind { get; set; }
public Rain rain { get; set; }
public Snow snow { get; set; }
public Clouds clouds { get; set; }
public Sys sys { get; set; }
[JsonProperty("base")]
public string _base { get; set; }
public int? visibility { get; set; }
public int? timezone { get; set; }
public int? id { get; set; }
public string name { get; set; }
public int? cod { get; set; }
}
As you can see there are also some classed containing their own properties as well.
I would like to check, if any of these is basically a null, so I can inform about missing value in Console. Right now I did some basic check using IFs for WeatherMain properties:
public static string PrepareResponse(WeatherMain input)
{
string cityName, main, visibility, wind, clouds, rain, snow, coord;
if (input.name == null)
cityName = "City name section not found\n";
else
cityName = $"\nCity name: {input.name}\n";
if (input.main == null)
main = "Main section not found\n";
else
{
main = $"Main parameters:\n\tTemperature: {input.main.temp}C\n\t" +
$"Temperature max.: {input.main.temp_max}C\n\tTemperature min.: {input.main.temp_min}C" +
$"\n\tFeels like: {input.main.feels_like}C\n\tPressure: {input.main.pressure}hPA\n\t" +
$"Humidity: {input.main.humidity}%\n";
}
if (input.visibility == null)
visibility = "Visibility section not found\n";
else
visibility = $"Visibility: {input.visibility}m\n";
if (input.wind == null)
wind = "Wind section not found\n";
else
wind = $"Wind:\n\tSpeed: {input.wind.speed}m/s\n\tDirection: {input.wind.deg}deg\n";
if (input.clouds == null)
clouds = "Clouds section not found\n";
else
clouds = $"Clouds: {input.clouds.all}%\n";
if (input.rain == null)
rain = "Rain section not found\n";
else
rain = $"Rain: {input.rain._1h}mm\n";
if (input.snow == null)
snow = "Snow section not found\n";
else
snow = $"Snow: {input.snow._1h}mm\n";
if (input.coord == null)
coord = "Coordinates section not found\n";
else
coord = $"Coordinates:\n\tLatitude: {input.coord.lat}\n\tLongitude: {input.coord.lon}\n";
string outputString = cityName + main + visibility + wind + clouds + rain + snow + coord;
return outputString;
}
I was thinking of putting those properties to a collection and checking them for null one by one and maybe changing the value to string.
I guess there are better ways to do that.
Any ideas?
you can do this by using Reflection namespace:
var weather = new WeatherMain
{
visibility = 2,
timezone = 5
};
Type t = weather.GetType();
Console.WriteLine("Type is: {0}", t.Name);
PropertyInfo[] props = t.GetProperties();
Console.WriteLine("Properties (N = {0}):",
props.Length);
foreach (var prop in props)
if (prop.GetValue(weather) == null)
{
Console.WriteLine(" {0} ({1}) is null", prop.Name,
prop.PropertyType.Name);
}
here is a sample fiddle: https://dotnetfiddle.net/MfV7KD
Using a descriptor class, you can consolidate the value access, the null messsage and the formatted output into one place, and then loop through them.
First, the Parameter class will describe each parameter:
public class Parameter {
public Func<WeatherMain, object> Value;
public string NullName;
public Func<WeatherMain, string> FormatSection;
}
Then, a static List will describe all the parameters you want to test and output:
public static List<Parameter> Parameters = new[] {
new Parameter { Value = w => w.name, NullName = "City name", FormatSection = w => $"City name: {w.name}" },
new Parameter { Value = w => w.main, NullName = "Main",
FormatSection = w => $"Main parameters:\n\tTemperature: {w.main.temp}C\n\t" +
$"Temperature max.: {w.main.temp_max}C\n\tTemperature min.: {w.main.temp_min}C" +
$"\n\tFeels like: {w.main.feels_like}C\n\tPressure: {w.main.pressure}hPA\n\t" +
$"Humidity: {w.main.humidity}%" },
new Parameter { Value = w => w.visibility, NullName = "Visibility", FormatSection = w => $"Visibility: {w.visibility}" },
new Parameter { Value = w => w.wind, NullName = "Wind", FormatSection = w => $"Wind:\n\tSpeed: {w.wind.speed}m/s\n\tDirection: {w.wind.deg}deg" },
new Parameter { Value = w => w.clouds, NullName = "Clouds", FormatSection = w => $"Clouds: {w.clouds.all}%" },
new Parameter { Value = w => w.rain, NullName = "Rain", FormatSection = w => $"Rain: {w.rain._1h}mm" },
new Parameter { Value = w => w.snow, NullName = "Snow", FormatSection = w => $"Snow: {w.snow._1h}mm" },
new Parameter { Value = w => w.coord, NullName = "Coordinates", FormatSection = w => $"Coordinates:\n\tLatitude: {w.coord.lat}\n\tLongitude: {w.coord.lon}" },
}.ToList();
Finally, you can produce the formatted response by looping through the list:
public static string PrepareResponse(WeatherMain input) {
var ans = new List<string>();
foreach (var p in Parameters) {
if (p.Value(input) == null)
ans.Add($"{p.NullName} section not found");
else
ans.Add(p.FormatSection(input));
}
return String.Join("\n", ans);
}

Compare two Complex Objects in c#

I am trying to compare two objects and want to identify the difference in objects.
It works fine if I don't use List. But when I use List, Although the List in both objects are equal, it says it's not equal and go to check for List difference. Since, for now, I don't have the condition for list comparison it crashes and gives me this exception System.Reflection.TargetParameterCountException: 'Parameter count mismatch.'
I have taken help from one of the stackoverflow post from here. Here is my code which I have written so far.
public class Program
{
public static void Main()
{
Employee emp1 = OldEmployee();
Employee emp2 = NewEmployee();
var list = GetDifferingProperties(emp1, emp2);
foreach (var s in list)
Console.WriteLine(s);
}
public static IList<string> GetDifferingProperties(object source, object target)
{
var sourceType = source.GetType();
var sourceProperties = sourceType.GetProperties();
var targetType = target.GetType();
var targetProperties = targetType.GetProperties();
var result = new List<string>();
foreach (var property in
(from s in sourceProperties
from t in targetProperties
where s.Name == t.Name &&
s.PropertyType == t.PropertyType &&
!Equals(s.GetValue(source, null), t.GetValue(target, null))
select new { Source = s, Target = t }))
{
// it's up to you to decide how primitive is primitive enough
if (IsPrimitive(property.Source.PropertyType))
{
result.Add(property.Source.Name);
}
else
{
foreach (var subProperty in GetDifferingProperties(
property.Source.GetValue(source, null),
property.Target.GetValue(target, null)))
{
result.Add(property.Source.Name);
}
}
}
return result;
}
private static bool IsPrimitive(Type type)
{
return type == typeof(string)
|| type == typeof(int) || type == typeof(int?)
|| type == typeof(double) || type == typeof(double?)
|| type == typeof(bool) || type == typeof(bool?)
|| type == typeof(Guid) || type == typeof(Guid?)
|| type == typeof(DateTime) || type == typeof(DateTime?);
}
public static string GetPropertyDisplayName(PropertyInfo pi)
{
var dp = pi.GetCustomAttributes(typeof(DisplayNameAttribute), true).Cast<DisplayNameAttribute>().SingleOrDefault();
return dp != null ? dp.DisplayName : pi.Name;
}
private static Employee NewEmployee()
{
var contactDetail = new ContactDetails();
contactDetail.ContactNumber = "123456789";
var employeeAddress = new Address();
employeeAddress.AddressLine1 = "Microsoft Corporation";
employeeAddress.AddressLine2 = "One Microsoft Way";
employeeAddress.City = "Redmond";
employeeAddress.State = "WA";
employeeAddress.Zip = "98052-6399";
employeeAddress.ContactDetails = new List<ContactDetails>() { contactDetail };
var employee = new Employee();
employee.FirstName = "Bill";
employee.LastName = "Gates";
employee.MiddleName = "Middle Name";
employee.IsActive = true;
employee.JoinDate = new DateTime(2015, 10, 15);
employee.ReleaseDate = new DateTime(2015, 10, 15);
employee.EmployeeAddress = employeeAddress;
return employee;
}
private static Employee OldEmployee()
{
var contactDetail = new ContactDetails();
contactDetail.ContactNumber = "123456789";
var employeeAddress = new Address();
employeeAddress.AddressLine1 = "Microsoft Corporation";
employeeAddress.AddressLine2 = "One Microsoft Way";
employeeAddress.City = "Redmond";
employeeAddress.State = "WA";
employeeAddress.Zip = "98052-6399";
employeeAddress.ContactDetails = new List<ContactDetails>() { contactDetail };
var employee = new Employee();
employee.FirstName = "Bill";
employee.LastName = "Gates";
employee.MiddleName = "Middle Name";
employee.IsActive = true;
employee.JoinDate = new DateTime(2015, 10, 15);
employee.ReleaseDate = new DateTime(2015, 10, 15);
employee.EmployeeAddress = employeeAddress;
return employee;
}
}
public class ContactDetails
{
public string ContactNumber { get; set; }
}
public class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public bool IsActive { get; set; }
public List<ContactDetails> ContactDetails { get; set; }
}
public class Employee
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public bool IsActive { get; set; }
public DateTime JoinDate { get; set; }
public DateTime? ReleaseDate { get; set; }
public Address EmployeeAddress { get; set; }
}
If I comment out the line employeeAddress.ContactDetails = new List<ContactDetails>() { contactDetail }; from oldEmployee and newEmployee, it works fine, and if there is any difference in EmployeeAddress it identifies it, and if there isn't any difference it doesn't. But if I uncomment this line employeeAddress.ContactDetails = new List<ContactDetails>() { contactDetail }; it takes out EmployeeAddress as it has difference, although it doesn't. Can some one please suggest what I am missing, I have spent a lot of time to identify the issue.
My Target to Achieve is, to make a list of properties that has a difference in Employee Object, for Example,
If FirstName in Employee has difference result list should
contain only FirstName
If FirstName in Employee and AddressLine1 in EmployeeAddress has difference result list should contain (FirstName, EmployeeAddress)
Similarly, If FirstName in Employee and ContactNumber in ContactDetails which is a List in Address has difference result list should contain (FirstName, EmployeeAddress), In this case result list still contains EmployeeAddress because ContactNumber is in EmployeeAddress which is in Employee, So our main focus is to get parent Properties that has difference is source and target object.
Point 1 and Point 2 are working as Expected. Iff I comment out employeeAddress.ContactDetails = new List<ContactDetails>() { contactDetail }; because it causing exception and also it coming with change although it doesn't have any change in source and target.
Any help would be highly appreciated, or you can suggest me some other way of implementation too.

Getting property DisplayName form resource type using Generics

I am developing a class that gets list of objects and export it to excel.
I am using EPPlus and generics.
Basically the solution is to:
Create a generic class that gets list of objects
Generate the header using GetProperties
Generate the data
My problem is to get the localized string for the header property
I have a class defined as follow:
public class OrderToExport
{
[Display(ResourceType = typeof(OrdersManagementStrings), Name = "OrderID")]
public int OrderID { get; set; }
public string UserName { get; set; }
// Shipping Information
[Display(ResourceType = typeof(OrdersManagementStrings), Name = "ShippingType")]
public Order.eShippingType ShippingType { get; set; }
[Display(ResourceType = typeof(OrdersManagementStrings), Name = "OrderDate")]
public DateTime OrderDate { get; set; }
public string InstituteName { get; set; }
public decimal TotalPrice { get; set; }
[Display(ResourceType = typeof(OrdersManagementStrings), Name = "OrderType")]
public Order.eOrderType EOrderType { get; set; }
public string ShipingAddress { get; set; }
[Display(ResourceType = typeof(OrdersManagementStrings), Name = "OrderStatus")]
public Order.eOrderStatus EOrderStatus { get; set; }
}
I have a Method:
public ExportToExcel(List<T> i_obj, string i_SheetName)
{
foreach (var prop in typeof(T).GetProperties())
{
//var n = prop.GetCustomAttributes(true).OfType<T>().Cast<T>();
ws.Cells[row, column].Value = GetDisplayName(prop);
column++;
}
}
private string GetDisplayName(PropertyInfo property)
{
var attrName = GetAttributeDisplayName(property);
if (!string.IsNullOrEmpty(attrName))
return attrName;
var metaName = GetMetaDisplayName(property);
if (!string.IsNullOrEmpty(metaName))
return metaName;
return property.Name.ToString();
}
private string GetAttributeDisplayName(PropertyInfo property)
{
var atts = property.GetCustomAttributes(true);
if (atts.Length == 0)
return null;
return (atts[0] as DisplayNameAttribute).DisplayName;
}
private string GetMetaDisplayName(PropertyInfo property)
{
var atts = property.DeclaringType.GetCustomAttributes(true);
if (atts.Length == 0)
return null;
var metaAttr = atts[0] as MetadataTypeAttribute;
var metaProperty =
metaAttr.MetadataClassType.GetProperty(property.Name);
if (metaProperty == null)
return null;
return GetAttributeDisplayName(metaProperty);
}
Now, I don't know how to get the sting stored in the resource manager (OrdersManagementStrings) for that property for example the localized property for OrderID .
Any help will be appreciated
Here is the answer
geekswithblogs.net/mapfel/archive/2008/11/01/126465.aspx
Thanks to #Avijit
Have a look here: http://geekswithblogs.net/mapfel/archive/2008/11/01/126465.aspx
To learn how to change the culture to use during runtime, see the second comment in the link:
switch (comboBox1.Text)
{
case "neutral":
Thread.CurrentThread.CurrentUICulture = new CultureInfo("");
break;
case "en-GB":
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-GB");
break;
case "de-DE":
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");
break;
}
string messageText = Messages.MsgSampleText;
MessageBox.Show(messageText);

Get Values From Complex Class Using Reflection

I have a class, which is created and populated from an xml string, I've simplified it for example purposes:
[XmlRoot("Person")]
public sealed class Person
{
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Location")]
public string Location { get; set; }
[XmlElement("Emails", Type = typeof(PersonEmails)]
public PersonEmails Emails { get; set; }
}
public class PersonEmails
{
[XmlElement("Email", Type = typeof(PersonEmail))]
public PersonEmail[] Emails { get; set; }
}
public class PersonEmail
{
[XmlAttribute("Type")]
public string Type { get; set; }
[XmlText]
public string Value { get; set; }
}
To extract the information, I'm trying to load them into another class, which is simply:
public class TransferObject
{
public string Name { get; set; }
public ObjectField[] Fields { get; set; }
}
public class ObjectField
{
public string Name { get; set; }
public string Value { get; set; }
}
I'm only populating "Fields" from the other object, which would simply be (Name = "Location", Value = "London"), but for Emails, (Name = "Email"+Type, Value = jeff#here.com)
Currently I can populate all the other fields, but I'm stuck with Emails, and knowing how to dig deep enough to be able to use reflection (or not) to get the information I need. Currently I'm using:
Person person = Person.FromXmlString(xmlString);
List<ObjectField> fields = new List<ObjectField>();
foreach (PropertyInfo pinfo in person.getType().GetProperties()
{
fields.Add(new ObjectField { Name = pinfo.Name, Value = pinfo.getValue(person, null).ToString();
}
How can I expand on the above to add all my emails to the list?
You are trying to type cast a complex values type to string value so you lost the data. Instead use following code:
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Name = "Person One";
person.Location = "India";
person.Emails = new PersonEmails();
person.Phones = new PersonPhones();
person.Emails.Emails = new PersonEmail[] { new PersonEmail() { Type = "Official", Value = "xyz#official.com" }, new PersonEmail() { Type = "Personal", Value = "xyz#personal.com" } };
person.Phones.Phones = new PersonPhone[] { new PersonPhone() { Type = "Official", Value = "789-456-1230" }, new PersonPhone() { Type = "Personal", Value = "123-456-7890" } };
List<ObjectField> fields = new List<ObjectField>();
fields = GetPropertyValues(person);
}
static List<ObjectField> GetPropertyValues(object obj)
{
List<ObjectField> propList = new List<ObjectField>();
foreach (PropertyInfo pinfo in obj.GetType().GetProperties())
{
var value = pinfo.GetValue(obj, null);
if (pinfo.PropertyType.IsArray)
{
var arr = value as object[];
for (var i = 0; i < arr.Length; i++)
{
if (arr[i].GetType().IsPrimitive)
{
propList.Add(new ObjectField() { Name = pinfo.Name + i.ToString(), Value = arr[i].ToString() });
}
else
{
var lst = GetPropertyValues(arr[i]);
if (lst != null && lst.Count > 0)
propList.AddRange(lst);
}
}
}
else
{
if (pinfo.PropertyType.IsPrimitive || value.GetType() == typeof(string))
{
propList.Add(new ObjectField() { Name = pinfo.Name, Value = value.ToString() });
}
else
{
var lst = GetPropertyValues(value);
if (lst != null && lst.Count > 0)
propList.AddRange(lst);
}
}
}
return propList;
}
}
Check this snippet out:
if(pinfo.PropertyType.IsArray)
{
// Grab the actual instance of the array.
// We'll have to use it in a few spots.
var array = pinfo.GetValue(personObject);
// Get the length of the array and build an indexArray.
int length = (int)pinfo.PropertyType.GetProperty("Length").GetValue(array);
// Get the "GetValue" method so we can extact the array values
var getValue = findGetValue(pinfo.PropertyType);
// Cycle through each index and use our "getValue" to fetch the value from the array.
for(int i=0; i<length; i++)
fields.Add(new ObjectField { Name = pinfo.Name, Value = getValue.Invoke(array, new object[]{i}).ToString();
}
// Looks for the "GetValue(int index)" MethodInfo.
private static System.Reflection.MethodInfo findGetValue(Type t)
{
return (from mi in t.GetMethods()
where mi.Name == "GetValue"
let parms = mi.GetParameters()
where parms.Length == 1
from p in parms
where p.ParameterType == typeof(int)
select mi).First();
}
You can definately do it with Reflection... You can take advantage of the fact that a Type can tell you if it's an array or not (IsArray)... and then take advantage of the fact that an Array has a method GetValue(int index) that will give you a value back.
Per your comment
Because Emails is a property within a different class, recursion should be used. However the trick is knowing when to go to the next level. Really that is up to you, but
if it were me, I would use some sort of Attribute:
static void fetchProperties(Object instance, List<ObjectField> fields)
{
foreach(var pinfo in instance.GetType().GetProperties())
{
if(pinfo.PropertyType.IsArray)
{
... // Code described above
}
else if(pinfo.PropertyType.GetCustomAttributes(typeof(SomeAttribute), false).Any())
// Go the next level
fetchProperties(pinfo.GetValue(instance), fields);
else
{
... // Do normal code
}
}
}

What is the best way to apply these changes to an object without duplicating code?

I am trying update a number of properties of one object from another and I wind up repeating this same code over and over again (i am showing an example with Name and LastName but i have 15 other properties with similar code).
But its important to Note that its NOT all properties so i can't blindly just copy everything.
public class Person
{
public bool UpdateFrom(Person otherPerson)
{
if (!String.IsNullOrEmpty(otherPerson.Name))
{
if (Name!= otherPerson.Name)
{
change = true;
Name = otherPerson.Name;
}
}
if (!String.IsNullOrEmpty(otherPerson.LastName))
{
if (LastName!= otherPerson.LastName)
{
change = true;
LastName = otherPerson.LastName;
}
}
return change;
}
}
is there a more elegant way to writing this code?
You could use an Expression to define which field you want to access, the code to handle the updates would look like this:-
Person one = new Person {FirstName = "First", LastName = ""};
Person two = new Person {FirstName = "", LastName = "Last"};
Person three = new Person ();
bool changed = false;
changed = SetIfNotNull(three, one, p => p.FirstName) || changed;
changed = SetIfNotNull(three, one, p => p.LastName) || changed;
changed = SetIfNotNull(three, two, p => p.FirstName) || changed;
changed = SetIfNotNull(three, two, p => p.LastName) || changed;
Note that the order in the || expression matters since .NET will short-circuit the evaluation if it can. Or as Ben suggests in the comments below, use changed |= ... as a simpler alternative.
The SetIfNotNull method relies on this other method that does a bit of Expression magic to convert a getter ino a setter.
/// <summary>
/// Convert a lambda expression for a getter into a setter
/// </summary>
public static Action<T, U> GetSetter<T, U>(Expression<Func<T, U>> expression)
{
var memberExpression = (MemberExpression)expression.Body;
var property = (PropertyInfo)memberExpression.Member;
var setMethod = property.GetSetMethod();
var parameterT = Expression.Parameter(typeof(T), "x");
var parameterU = Expression.Parameter(typeof(U), "y");
var newExpression =
Expression.Lambda<Action<T, U>>(
Expression.Call(parameterT, setMethod, parameterU),
parameterT,
parameterU
);
return newExpression.Compile();
}
public static bool SetIfNotNull<T> (T destination, T source,
Expression<Func<T, string>> getter)
{
string value = getter.Compile()(source);
if (!string.IsNullOrEmpty(value))
{
GetSetter(getter)(destination, value);
return true;
}
else
{
return false;
}
}
Using Func and Action delegates you can do it like this:
public class Person
{
public string Name { get; set; }
public string LastName { get; set; }
public bool UpdateFrom(Person otherPerson)
{
bool change = false;
change = Check(otherPerson.Name, p => p.Name, (p, val) => p.Name = val);
change = change ||
Check(otherPerson.LastName, p => p.LastName, (p, val) => p.LastName = val);
return change;
}
public bool Check(string value, Func<Person, string> getMember, Action<Person, string> action)
{
bool result = false;
if (!string.IsNullOrEmpty(value))
{
if (getMember(this) != value)
{
result = true;
action(this, value);
}
}
return result;
}
}
You can use reflecton to do it.. here's an example implementation (need to add extra code to handle arrays etc.)
public class Person
{
public bool UpdateFromOther(Person otherPerson)
{
var properties =
this.GetType()
.GetProperties(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty
| BindingFlags.GetProperty);
var changed = properties.Any(prop =>
{
var my = prop.GetValue(this);
var theirs = prop.GetValue(otherPerson);
return my != null ? !my.Equals(theirs) : theirs != null;
});
foreach (var propertyInfo in properties)
{
propertyInfo.SetValue(this, propertyInfo.GetValue(otherPerson));
}
return changed;
}
public string Name { get; set; }
}
[Test]
public void Test()
{
var instance1 = new Person() { Name = "Monkey" };
var instance2 = new Person() { Name = "Magic" };
var instance3 = new Person() { Name = null};
Assert.IsFalse(instance1.UpdateFromOther(instance1), "No changes should be detected");
Assert.IsTrue(instance2.UpdateFromOther(instance1), "Change is detected");
Assert.AreEqual("Monkey",instance2.Name, "Property updated");
Assert.IsTrue(instance3.UpdateFromOther(instance1), "Change is detected");
Assert.AreEqual("Monkey", instance3.Name, "Property updated");
}
This is just my comment typed out, you can refer to the comments to your question about further details about this technique.
Define this class:
[AttributeUsage(AttributeTargets.Property)]
public sealed class CloningAttribute : Attribute
{
}
In your Person class:
[Cloning] // <-- applying the attribute only to desired properties
public int Test { get; set; }
public bool Clone(Person other)
{
bool changed = false;
var properties = typeof(Person).GetProperties();
foreach (var prop in properties.Where(x => x.GetCustomAttributes(typeof(CloningAttribute), true).Length != 0))
{
// get current values
var myValue = prop.GetValue(this, null);
var otherValue = prop.GetValue(other, null);
if (prop.PropertyType == typeof(string))
{
// special treatment for string:
// ignore if null !!or empty!!
if (String.IsNullOrEmpty((string)otherValue))
{
continue;
}
}
else
{
// do you want to copy if the other value is null?
if (otherValue == null)
{
continue;
}
}
// compare and only check 'changed' if they are different
if (!myValue.Equals(otherValue))
{
changed = true;
prop.SetValue(this, otherValue, null);
}
}
return changed;
}
You can create generic rewriting tool with will look on properties with particular attribute:
public class Updater
{
public static bool Update(object thisObj, object otherObj)
{
IEnumerable<PropertyInfo> props = thisObj.GetType().GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(UpdateElementAttribute)));
bool change = false;
foreach (var prop in props)
{
object value = prop.GetValue(otherObj);
if (value != null && (value is string || string.IsNullOrWhiteSpace((string)value)))
{
if (!prop.GetValue(thisObj).Equals(value))
{
change = true;
prop.SetValue(thisObj, value);
}
}
}
return change;
}
}
And then just use it:
public class Person
{
public bool UpdateFrom(Person otherPerson)
{
return Updater.Update(this, otherPerson);
}
[UpdateElement]
public string Name { get; set; }
[UpdateElement]
public string LastName { get; set; }
}

Categories