Using reflection to iterate a class properties that has nested classes - c#

I found this answer here at SO, Get nested property values through reflection C#, though when I run it in my case, it also tries to dump/recurse on e.g. a string's property, like Name, and when, it throws an exception.
My classes look like this
public class MyModels
{
public int Id { get; set; }
public DateTime EditDate { get; set; }
public string EditBy { get; set; }
}
public class Person
{
public string Name { get; set; }
}
public class Organization
{
public Person Person { get; set; }
public Organization()
{
Person = new Person();
}
public string Name { get; set; }
}
public class Company : MyModels
{
public Organization Organization { get; set; }
public Company()
{
Organization = new Organization();
}
public string Description { get; set; }
}
And here's the code from the linked answer
var objtree = "";
void DumpObjectTree(object propValue, int level = 0)
{
if (propValue == null)
return;
var childProps = propValue.GetType().GetProperties();
foreach (var prop in childProps)
{
var name = prop.Name;
var value = prop.GetValue(propValue, null);
// add some left padding to make it look like a tree
objtree += ("".PadLeft(level * 4, ' ') + $"{name} = {value}") + Environment.NewLine;
// call again for the child property
DumpObjectTree(value, level + 1);
}
}
DumpObjectTree(itemData);
What I want is to iterate all the properties and check their value.
When I run the above code sample:
it first finds Organization, and recurse
at 1st level it finds Person, and recurse
at 2nd level if finds Name, and recurse
at 3rd level it throws an exception when it tries to GetValue for Name
If I remove my nested classes, and run it:
it first finds Description, and recurse
at 1st level it throws an exception when it tries to GetValue for Description
How do I make it to not try to dump/recurse on properties of type string, datetime, etc., like e.g. Name, Description?
The exception message says: "Parameter count mismatch."
As a note , the expected output/content in the objtree variable is e.g.
Organization = MyNameSpace.Models.Organization
Person = MyNameSpace.Models.Person
Name = TestName
Name = TestCompany
Description = Some info about the company...
Id = 1
EditDate = 31/08/2019
EditBy = user#domain.com

The reason for the exception is that string has a property named Chars. You normally don't see this property, because it's the indexer used when you do something like char c = myString[0];.
This property obviously needs a paramter (the index), and since you don't provide one, an exception is thrown.
To filter the types you don't want to recurse you need to extend the first line in the method. For example
if (propValue == null) return;
if (propValue.GetType().Assembly != Assembly.GetExecutingAssembly())
return;
This will only recurse through types declared in your assembly. If you want special filtering you need to adjust it.
Your current specification ("of type string, datetime etc") is not specific enough to give an exact solution, but I think the idea is clear.
Note that this won't prevent an exception to be raised if you declare an indexer in your own classes. So a better way might be to check for indexers directly:
foreach (var prop in childProps)
{
if (prop.GetIndexParameters().Any()) continue;
Second note: The current code has another flaw: You should keep track of which types you already dumped and abort the recursion when you come across a type the second time. That's possibly the reason for the exception at DateTime. A DateTime has a Date property, which is - hurray - of type DateTime. And so your objtree string grows infinitly until an OutOfMemoryException or StackOverflowException is thrown.

You need to skip recursion when:
Property is a value type
Property is a string
Property value contains reference to the object from the previous recursion level (ie, ParentObject) so that you don't get a stack overflow exception
Edit: Also when property is a collection type. If you want to get creative, you can have your recursor iterate through each object in the collection and then recurse through those
This PropertyInfo recursor seems to do the trick.
[Flags]
public enum PropertyRecursionOverflowProtectionType
{
SkipSameReference,
SkipSameType
}
public class PropertyRecursionBot
{
public object ParentObject { get; set; }
public object CurrentObject { get; set; }
public PropertyInfo PropertyInfo { get; set; }
public Type ParentType { get; set; }
public int Level { get; set; }
}
public static IEnumerable<PropertyRecursionBot> GetAllProperties(object entity,
PropertyRecursionOverflowProtectionType overflowProtectionType = PropertyRecursionOverflowProtectionType.SkipSameReference)
{
var type = entity.GetType();
var bot = new PropertyRecursionBot { CurrentObject = entity };
IEnumerable<PropertyRecursionBot> GetAllProperties(PropertyRecursionBot innerBot, PropertyInfo[] properties)
{
var currentParentObject = innerBot.ParentObject;
var currentObject = innerBot.CurrentObject;
foreach (var pi in properties)
{
innerBot.PropertyInfo = pi;
var obj = pi.GetValue(currentObject);
innerBot.CurrentObject = obj;
//Return the property and value only if it's a value type or string
if (pi.PropertyType == typeof(string) || !pi.PropertyType.IsClass)
{
yield return innerBot;
continue;
}
//This overflow protection check will prevent stack overflow if your object has bidirectional navigation
else if (innerBot.CurrentObject == null ||
(overflowProtectionType.HasFlag(PropertyRecursionOverflowProtectionType.SkipSameReference) && innerBot.CurrentObject == currentParentObject) ||
(overflowProtectionType.HasFlag(PropertyRecursionOverflowProtectionType.SkipSameType) && innerBot.CurrentObject.GetType() == currentParentObject?.GetType()))
{
continue;
}
innerBot.Level++;
innerBot.ParentObject = currentObject;
foreach (var innerPi in GetAllProperties(innerBot, pi.PropertyType.GetProperties()))
{
yield return innerPi;
}
innerBot.Level--;
innerBot.ParentObject = currentParentObject;
innerBot.CurrentObject = obj;
}
}
foreach (var pi in GetAllProperties(bot, type.GetProperties()))
{
yield return pi;
}
}
Use it like this:
public class RecursionTest
{
public string StringValue { get; set; }
public int IntValue { get; set; }
public RecursionTest Test { get; set; }
public RecursionTest ParentTest { get; set; }
}
var rec1 = new RecursionTest
{
IntValue = 20,
StringValue = Guid.NewGuid().ToString()
};
rec1.Test = new RecursionTest
{
IntValue = 30,
StringValue = Guid.NewGuid().ToString(),
ParentTest = rec1
};
rec1.Test.Test = new RecursionTest
{
IntValue = 40,
StringValue = Guid.NewGuid().ToString(),
ParentTest = rec1.Test
};
foreach (var bot in GetAllProperties(rec1, PropertyRecursionOverflowProtectionType.SkipSameReference))
{
Console.WriteLine($"{new string(' ', bot.Level * 2)}{bot.PropertyInfo.Name}: {bot.CurrentObject}");
}

Related

Updating Custom Class in List<T>

I am trying to update a List which is a List of Interfaces to concrete classes.
I add to the List each Market type i am interested in, for this Example these Markets are A and B
I loop over all the markets, (sample provided with 3 markets A B & C, we are only interested in A and B) And determine which is of interest to us.
Once found we pass this to an extraction method too do its work and create an instance of the Correct Market_ class type.
This all works fine, but when i try to update the list with the Updates it does not get reflected in the List.
Code below, any Suggestions?
Thanks
public class Test
{
public Test()
{
TheMarkets MarketsToUpdate = new TheMarkets();
List<SpecificCompanyMarket> lstMarks = new List<SpecificCompanyMarket>();
lstMarks.Add(new SpecificCompanyMarket(1234, "A", "Some HTML DATA HERE"));
lstMarks.Add(new SpecificCompanyMarket(5874, "B", "Some HTML DATA HERE"));
lstMarks.Add(new SpecificCompanyMarket(2224, "C", "Some HTML DATA HERE"));
foreach (var item in lstMarks)
{
if (MarketsToUpdate.IsMarketWeAreInterestedIn(item.MarketName))
{
ITheMarkets MarkToUpdate = ExtractMarketData(item);
var obj = MarketsToUpdate.MarketsWeAreInterestedIn.FirstOrDefault(x => x.MarketName() == "A");
if (obj != null)
{
obj = MarkToUpdate;
}
}
}
//Look At MarketsToUpdate Now and the item has not changed, still original values
//I was expecting to see the new values for the fields in A, not the default 0's
}
public ITheMarkets ExtractMarketData(SpecificCompanyMarket item)
{
ITheMarkets market = null;
if (item.MarketName.ToUpper() == "A")
{
Market_A marketType = new Market_A();
marketType.SomeValue1 = 123;
marketType.SomeValue2 = 158253;
market = marketType;
}
//Other Market extractions here
return market;
}
}
public class SpecificCompanyMarket
{
public int MarketId { get; set; }
public string MarketName { get; set; }
public string MarketDataHTML { get; set; }
public SpecificCompanyMarket(int MID, string MName, string MData)
{
MarketId = MID;
MarketName = MName;
MarketDataHTML = MData;
}
}
public class TheMarkets
{
public List<ITheMarkets> MarketsWeAreInterestedIn = new List<ITheMarkets>();
public TheMarkets()
{
Market_A A = new Market_A();
Market_B B = new Market_B();
MarketsWeAreInterestedIn.Add(A);
MarketsWeAreInterestedIn.Add(B);
}
public bool IsMarketWeAreInterestedIn(string strMarketName)
{
bool blnRetVal = false;
foreach (var item in MarketsWeAreInterestedIn)
{
if (item.MarketName().ToUpper().Trim().Equals(strMarketName.ToUpper().Trim()))
{
blnRetVal = true;
break;
}
}
return blnRetVal;
}
}
public interface ITheMarkets
{
string MarketName();
}
public class Market_A : ITheMarkets
{
public string LabelType { get; private set; }
public double SomeValue1 { get; set; }
public double SomeValue2 { get; set; }
public double SomeValue3 { get; set; }
public Market_A()
{
LabelType = "A";
}
public string MarketName()
{
return LabelType;
}
}
public class Market_B : ITheMarkets
{
public string LabelType { get; private set; }
public List<string> SomeList { get; set; }
public double SomeValue { get; set; }
public Market_B()
{
LabelType = "B";
}
public string MarketName()
{
return LabelType;
}
}
This is a short example to get you going. Loop through your list, find the object you want to update, create a new object of that type and then find the original objects index in the list and overwrite it in place. You are essentially just replacing the object in the list with a new one not mutating the existing one.
foreach (var item in lstMarks)
{
//your code to get an object with data to update
var yourObjectToUpdate = item.GetTheOneYouWant();
//make updates
yourObjectToUpdate.SomeProperty = "New Value";
int index = lstMarks.IndexOf(item);
lstMarks[index] = yourObjectToUpdate;
}
You are extracting an obj from marketWeAreInterestedIn list using LINQ's firstOrDefault extension. This is a new object and not a reference to the obj in that list. Therefore, no updates will be reflected in the object inside that list. Try using 'indexof'
You are not storing "list of interfaces" in your list. List<T> stores an array of pointers to objects that support T interface. Once you enumerate (with Linq in your case) your list, you copy a pointer from list, which is not associated with list itself in any way. It is just a pointer to your instance.
To do what you want, you will have to build new list while enumerating the original one, adding objects to it, according to your needs, so the second list will be based on the first one but with changes applied that you need.
You can also replace specific instance at specific index instead of building new list in your code, but to do this you will need to enumerate your list with for loop and know an index for each item:
list[index] = newvalue;
But there is a third solution to update list item directly by Proxying them. This is an example
class ItemProxy : T { public T Value { get; set; } }
var list = new List<ItemProxy<MyClass>>();
list.Insert(new ItemProxy { Value = new MyClass() });
list.Insert(new ItemProxy { Value = new MyClass() });
list.Insert(new ItemProxy { Value = new MyClass() });
foreach(var item in list)
if(item // ...)
item.Value = new MyClass(); // done, pointer in the list is updated.
Third is the best case for perfomance, but it will be better to use this proxying class for something more than just proxying.

Create webservice for logging

I am trying to create a web service to handle all logging for application asynchronous, where I can send two of the same object and compare each property. Then create of log of property that changed.
The issue i am having, i don't know how can the apicontroller accept any object:
This is i have so far:
public class PropertyLogApiCall
{
public string Id { get; set; }
public string Username { get; set; }
public string Task { get; set; }
public object OldEntity { get; set; }
public object NewEntity { get; set; }
public string OldEntityType { get; set; }
public string NewEntityType { get; set; }
}
POST - ApiController
public void Post(PropertyLogApiCall paramList)
{
try
{
var id = paramList.Id;
var username = paramList.Username;
var task = paramList.Task;
var newType = Type.GetType(paramList.NewEntityType);
var oldType = Type.GetType(paramList.OldEntityType);
var newEntity = Convert.ChangeType(paramList.NewEntity, newType);
var oldEntity = Convert.ChangeType(paramList.OldEntity, oldType);
var properties = oldEntity.GetType().GetProperties();
var logsToSave = new List<PropertyLog>();
var dateTimeStamp = DateTime.Now;
foreach (var property in properties)
{
var oldValue = property.GetValue(oldEntity, null);
var newValue = newEntity.GetType().GetProperty(property.Name).GetValue(newEntity, null);
var propertyType = property.PropertyType;
var name = oldEntity.GetType().Name;
var propName = property.Name;
PropertyLog log = null;
if (propertyType == typeof(string))
{
log = CreateLogString(oldValue, newValue);
}
else if (propertyType == typeof(int))
{
log = CreateLogInt(oldValue, newValue);
}
if (log != null)
{
log.Created = dateTimeStamp;
log.EntityId = id;
log.Username = username;
log.EntityName = name;
log.Property = propName;
log.Task = task;
logsToSave.Add(log);
}
}
//Save Logs
.....
}
catch (Exception e)
{
//send email
}
This doesn't work because most of the time newType/oldType is null. And some entity don't implement the IConvertible interface.
Is this even possible to do?
As you say that in many occassions you don't have the object type, you need to do it dynamically. It's impossible to recreate an object if you don't know its type.
Change the parameter of your action to dynamic, and the object will be dynamically created including all the properties coming in the request. Then you need to step through all the properties of the OldEntity and compare them with those on the NewEntity. If a property can be a complex object with additional properties you'll have to do it recursively. However in SO you have this information: How do I enumerate through a JObject?
You can get ideas from this SO Q&A: Comparing dynamic objects in C#.
Alternatively, if you're posting it as JSON, you can receive the parameter as a JObject, and make the same opeartions on it. At this moment http://www.newtonsoft.com/ is down, so I cannot give you an exact pointer to JObject docs.

In Reflection, how to set a correct value for a property?

My target is to find all the "string" type properties, and assign them to a specific string value, like "This is a testing string".
I can find all the string type properties in a class now, but always have problems when assigning string value to the property in a class, which is the class property of another class.
public class Credit_Card
{
public string brand { get; set; }
public int billing_phone { get; set; }
public string credit_card_verification_number { get; set; }
public Expiration expiration { get; set; }
}
public class Expiration
{
public string month { get; set; }
public string year { get; set; }
}
class Program
{
static void Main(string[] args)
{
Credit_Card credcard = new Credit_Card { brand = "Visa", billing_phone = 12345, credit_card_verification_number = "1234", expiration = new Expiration { month = "11", year = "2016" } };
foreach (PropertyInfo prop in GetStringProperties(credcard.GetType()))
{
prop.SetValue(credcard,"testing string!!",null);
Console.WriteLine(prop.GetValue(credcard,null));
}
Console.ReadLine();
}
public static IEnumerable<PropertyInfo> GetStringProperties(Type type)
{
return GetStringProperties(type, new HashSet<Type>());
}
public static IEnumerable<PropertyInfo> GetStringProperties(Type type, HashSet<Type> alreadySeen)
{
foreach (var prop in type.GetProperties())
{
var propType = prop.PropertyType;
if (propType == typeof(string))
yield return prop;
else if (alreadySeen.Add(propType))
foreach (var indirectProp in GetStringProperties(propType, alreadySeen))
yield return indirectProp;
}
}
}
It always throws the exception when the loop runs to "month" property of Expiration class.
How can I assign the correct value into the correct instance?
If you wanted to assign month and year values you would need to get their PropInfo by doing Type.GetType(CredCard.Expiration).Properties because they aren't properties of credcard but are properties of credcard.Expiration.
You would have to check for the properties and assign them to credcard.Expiration rather than credcard.
// prop.Name == "month" || prop.Name == "year"
prop.SetValue(credcard.Expiration, "somestring");
Another alternative would be to assign a new Expiration object to the Expiration property:
if(prop.Name.Equals("Expiration"))
{
var expiration = new Expiration
{
month = "someString",
year = "someString"
};
prop.SetValue(credcard,expiration);
}
Looking at the code, you are grabbing all string properties on Credit_Card then recursively grabbing all string properties on child objects.
Your problem is coming from the fact that it is returning string month from Expiration. You then are attempting to set the Expiration.month property on your Credit_Card instance credcard.
That does not work. The target must match the same Type that declared the PropertyInfo. This is why you are getting:
System.Reflection.TargetException was unhandled, and the messagei is
"Object does not match target type"
You need to split your loop up so you do them in correct hierarchy chain and have the correct instance (target) that will be updated with your data.

How do I Find an object up the list I am iterating through

I am iterating through a List of objects of Type "prvEmployeeIncident".
The object has the following properties:
public DateTime DateOfIncident { get; set; }
public bool IsCountedAsAPoint;
public decimal OriginalPointValue;
public bool IsFirstInCollection { get; set; }
public bool IsLastInCollection { get; set; }
public int PositionInCollection { get; set; }
public int DaysUntilNextPoint { get; set; }
public DateTime DateDroppedBySystem { get; set; }
public bool IsGoodBehaviorObject { get; set; }
My List is sorted by the DateOfIncident property. I would like to find the next object up the list where IsCounted == true and change it to IsCounted = false.
One question:
1) How do I find this object up the list ?
If I understand your question correctly, you can use LINQ FirstOrDefault:
var nextObject = list.FirstOrDefault(x => x.IsCountedAsAPoint);
if (nextObject != null)
nextObject.IsCountedAsAPoint = false;
If I understand correctly this can be solved with a simple foreach loop. I don't exactly understand your emphasis on "up" as you don't really move up a list, you traverse it. Anyways, the following code snippet finds the first Incident where IsCounted is true and changes it to false. If you're starting from a given position change the for each loop to a for loop and start at i = currentIndex with the exit condition being i < MyList.Count. Leave the break statement to ensure you only modify one Incident object.
foreach (prvEmployeeIncident inc in MyList)
{
if (inc.IsCountedAsAPoint)
{
inc.IsCountedAsAPoint = false;
break;
}
}
You can use List(T).FindIndex to search up the list.
Example:
public class Foo
{
public Foo() { }
public Foo(int item)
{
Item = item;
}
public int Item { get; set; }
}
var foos = new List<Foo>
{
new Foo(1),
new Foo(2),
new Foo(3),
new Foo(4),
new Foo(5),
new Foo(6)
};
foreach (var foo in foos)
{
if(foo.Item == 3)
{
var startIndex = foos.IndexOf(foo) + 1;
var matchedFooIndex = foos.FindIndex(startIndex, f => f.Item % 3 == 0);
if(matchedFooIndex >= startIndex) // Make sure we found a match
foos[matchedFooIndex].Item = 10;
}
}
Just be sure you do not modify the list itself since that will throw an exception.

Casting/unboxing generic types

Good day,
I have a class that is used to store a value of Type T that I don't know what the type will be until runtime. I want to unbox/cast, not sure what the correct term is, a specific type (in this case a nullable decimal) back to type object.
Please forgive my code layout:
The class snippet:
public abstract class Types
{
public class ValueField<T>
{
[XmlAttribute]
public int TemplateID { get; set; }
[XmlAttribute]
public int FieldID { get; set; }
[XmlIgnore]
[ScriptIgnore]
public TemplateApprovalField Field { get; set; }
[XmlIgnore]
[ScriptIgnore]
public InstanceTemplateActivityValues Values { get; set; }
[XmlAttribute]
public T Value { get; set; }
}
}
The function snippet:
I am stuck at the line "values.Add((Types.ValueField)field);", don't know how to cast it. At that moment, var field is a Types.ValueField.
Values = new Func<XmlDocument, List<Types.ValueField<object>>>(xml =>
{
List<Types.ValueField<object>> values = new List<Types.ValueField<object>>();
if (xml != null)
{
foreach (XmlNode node in xml.SelectNodes("//Field"))
{
if (node.Attributes["Type"].Value == "Numeric")
{
var field = new Types.ValueField<decimal?>()
{
Field = ApprovalFields.Find(f => f.FieldID == int.Parse(node.Attributes["ID"].Value)),
FieldID = int.Parse(node.Attributes["ID"].Value),
TemplateID = int.Parse(node.SelectSingleNode("../#ID").Value)
};
field.Value = new Func<string, decimal?>(val =>
{
if (string.IsNullOrEmpty(val))
return null;
else
{
decimal parsed = 0;
if (decimal.TryParse(val, out parsed))
return parsed;
}
return null;
})(node.InnerText);
values.Add((Types.ValueField<object>)field); //This is where my problem occurs...
}
}
}
return values;
})(row["Form_Values"] != DBNull.Value ?
new XmlDocument() { InnerXml = row["Form_Values"].ToString() } : null)
Any input will be greatly appreciated.
Regards
YP
You're creating a ValueField<decimal?>. That isn't a ValueField<object> - your ValueField<T> class isn't covariant in T, and couldn't be. (Classes can't be covariant, and your API includes T in "in" positions too.)
To demonstrate why this mustn't work:
Value<decimal?> foo = new Value<decimal?>();
Value<object> bar = foo; // Imagine this worked
bar.Value = "hello";
decimal? x = foo.Value;
What would you expect that to do? Everything other than the second line is above reproach, so we must make the second line fail, which it does.
The simple answer here is to create a Value<object> in the first place, instead of a Value<decimal?>.

Categories

Resources