C# - Reflection using Generics: Problem with Nested collections of ILists - c#

I would like to be able to print object properties, and I've hit a snag when I hit nested collections of the iLists.
foreach (PropertyInformation p in properties)
{
//Ensure IList type, then perform recursive call
if (p.PropertyType.IsGenericType)
{
// recursive call to PrintListProperties<p.type?>((IList)p," ");
}
Can anyone please offer some help?
Cheers
KA

I'm just thinking aloud here. Maybe you can have a non generic PrintListProperties method that looks something like this:
private void PrintListProperties(IList list, Type type)
{
//reflect over type and use that when enumerating the list
}
Then, when you come across a nested list, do something like this:
if (p.PropertyType.IsGenericType)
{
PringListProperties((Ilist)p,p.PropertyType.GetGenericArguments()[0]);
}
Again, haven't tested this, but give it a whirl...

foreach (PropertyInfo p in props)
{
// We need to distinguish between indexed properties and parameterless properties
if (p.GetIndexParameters().Length == 0)
{
// This is the value of the property
Object v = p.GetValue(t, null);
// If it implements IList<> ...
if (v != null && v.GetType().GetInterface("IList`1") != null)
{
// ... then make the recursive call:
typeof(YourDeclaringClassHere).GetMethod("PrintListProperties").MakeGenericMethod(v.GetType().GetInterface("IList`1").GetGenericArguments()).Invoke(null, new object[] { v, indent + " " });
}
Console.WriteLine(indent + " {0} = {1}", p.Name, v);
}
else
{
Console.WriteLine(indent + " {0} = <indexed property>", p.Name);
}
}

p.PropertyType.GetGenericArguments()
will give you an array of the type arguments. (in ths case, with just one element, the T in IList<T>)

var dataType = myInstance.GetType();
var allProperties = dataType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var listProperties =
allProperties.
Where(prop => prop.PropertyType.GetInterfaces().
Any(i => i == typeof(IList)));

Related

c# Object Comparison With Complex Objects

I have a generic object comparison method which I use to compare two models with the same structure.
public static List<Variance> DetailedCompare<T>(this T val1, T val2)
{
var variances = new List<Variance>();
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties.Where(t => t.IsMarkedWith<IncludeInComparisonAttribute>()))
{
var v = new Variance
{
PropertyName = property.Name,
ValA = property.GetValue(val1, null),
ValB = property.GetValue(val2, null)
};
if (v.ValA == null && v.ValB == null) { continue; }
if (v.ValA != null && !v.ValA.Equals(v.ValB))
{
variances.Add(v);
}
}
return variances;
}
The problem I have is that sometimes an object is passed to it that may contain a list of other objects within it. Because it only compares at the top level it just returns that the object array was changed. Ideally I would like it to go through the nested array and look at the changed values as well.
Ideally I think it should probably make a recursive call when it finds an object array. Any ideas how I might go about this?
Edit - with working examples
Here are some .net fiddle examples of how this is meant to work.
This is the first code example that doesn't search down through the nested objects and just reports that the collection has changed (as per the code above):
https://dotnetfiddle.net/Cng7GI
returns:
Property: NumberOfDesks has changed from '5' to '4'
Property: Students has changed from 'System.Collections.Generic.List1[Student]' to 'System.Collections.Generic.List1[Student]'
Now if I try and call the DetailedCompare if I find a nested array using:
if (v.ValA is ICollection)
{
Console.WriteLine("I found a nested list");
variances.AddRange(v.ValA.DetailedCompare(v.ValB));
}
else if(v.ValA != null && !v.ValA.Equals(v.ValB)){
variances.Add(v);
}
it doesn't look like the recursive call works
https://dotnetfiddle.net/Ns1tx5
as I just get:
I found a nested list
Property: NumberOfDesks has changed from '5' to '4'
If I add:
var list = v.ValA.DetailedCompare<T>(v.ValB);
inside the Collection check, I get an error that:
object does not contain a definition for 'DetailedCompare' ... Cannot convert instance argument type 'object' to T
really what I want from it is just a single array of all the property names and their value changes.
Property: NumberOfDesks has changed from '5' to '4'
Property: Id has changed from '1' to '4'
Property: FirstName has changed from 'Cheshire' to 'Door'
etc
Calling the method recursively is the issue here.
If we call a method DetailedCompare recursively passing as parameters two objects all its fine - as we can get their properties and compare them.
However when we call DetailedCompare recursively passing a two list of objects - we can not just get the properties of those lists - but we need to traverse and get the properties of those list and compare their value.
IMHO it would be better to separate the logic using a helper method - so when we find a nested list - we can tackle the logic as I have described above.
This is the Extension class I have written
public static class Extension
{
public static List<Variance> Variances { get; set; }
static Extension()
{
Variances = new List<Variance>();
}
public static List<Variance> DetailedCompare<T>(this T val1, T val2)
{
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
var v = new Variance
{
PropertyName = property.Name,
ValA = property.GetValue(val1, null),
ValB = property.GetValue(val2, null)
};
if (v.ValA == null && v.ValB == null)
{
continue;
}
if (v.ValA is ICollection)
{
Console.WriteLine("I found a nested list");
DetailedCompareList(v.ValA,v.ValB);
}
else if (v.ValA != null && !v.ValA.Equals(v.ValB))
{
Variances.Add(v);
}
}
return Variances;
}
private static void DetailedCompareList<T>(T val1, T val2)
{
if (val1 is ICollection collection1 && val2 is ICollection collection2)
{
var coll1 = collection1.Cast<object>().ToList();
var coll2 = collection2.Cast<object>().ToList();
for (int j = 0; j < coll1.Count; j++)
{
Type type = coll1[j].GetType();
PropertyInfo[] propertiesOfCollection1 = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
PropertyInfo[] propertiesOfCollection2 = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < propertiesOfCollection1.Length; i++)
{
var variance = new Variance
{
PropertyName = propertiesOfCollection1[i].Name,
ValA = propertiesOfCollection1[i].GetValue(coll1[j]),
ValB = propertiesOfCollection2[i].GetValue(coll2[j])
};
if (!variance.ValA.Equals(variance.ValB))
{
Variances.Add(variance);
}
}
}
}
}
}
With the following result:
Limitations
This approach is bounded by the definition of your objects - hence it can only work with 1 level of depth.

"z" is a variable but used like type

I am trying to use generic list as cast of the object. i have seen related questions and tried but didn't get, what i expected.
In the below code i am setting the value to the main response.
Here is code:
Type type = Type.GetType("className, AssemblyName"); <br/>
var z = typeof(List<>).MakeGenericType(type);
foreach (var key in abc.Keys)
{
var value = abc[key];
foreach (var property in ((List<z>)output.Response)[0].GetType().GetProperties().Where(p => p.CanRead && p.GetMethod.IsPublic).)
{
if(property.Name == ((List<Document>)value)[0].GetType().Name)
{
PropertyInfo propertyInfo = ((List<Policy>)output.Response)[0].GetType().GetProperty(property.Name);
propertyInfo.SetValue(((List<Policy>)output.Response)[0], ((List<Document>)value));
}
}
}
while accessing z in list. i am unable to do that. I have stuck from past few hour.Please help me out.
Thanks in Advance!

recursive error handler

I have the following code where I'm trying to get all the properties of an object as well as the property values. Some properties can be collections or collections of collections, so I tried to set up a recursive function for those types. Unfortunately it's not working, erroring on this line
if (property.GetValue(item, null) is IEnumerable)
and I don't know what needs changed. Can anyone help here? Thanks.
public static string PackageError(IEnumerable<object> obj)
{
var sb = new StringBuilder();
foreach (object o in obj)
{
sb.Append("<strong>Object Data - " + o.GetType().Name + "</strong>");
sb.Append("<p>");
PropertyInfo[] properties = o.GetType().GetProperties();
foreach (PropertyInfo pi in properties)
{
if (pi.GetValue(o, null) is IEnumerable && !(pi.GetValue(o, null) is string))
sb.Append(GetCollectionPropertyValues((IEnumerable)pi.GetValue(o, null)));
else
sb.Append(pi.Name + ": " + pi.GetValue(o, null) + "<br />");
}
sb.Append("</p>");
}
return sb.ToString();
}
public static string GetCollectionPropertyValues(IEnumerable collectionProperty)
{
var sb = new StringBuilder();
foreach (object item in collectionProperty)
{
PropertyInfo[] properties = item.GetType().GetProperties();
foreach (var property in properties)
{
if (property.GetValue(item, null) is IEnumerable)
sb.Append(GetCollectionPropertyValues((IEnumerable)property.GetValue(item, null)));
else
sb.Append(property.Name + ": " + property.GetValue(item, null) + "<br />");
}
}
return sb.ToString();
}
I would suggest using an existing serialization mechanism, such as XML serialization or JSON serialization, to provide this information if you are trying to make it generic.
It sounds like that particular property is an indexer, so it's expecting you to pass index values to the GetValue method. There is no easy way to take an indexer and determine what values are valid to pass as indices, in the general case, as a class is free to implement an indexer however it wants. For instance, a Dictionary keyed on a string has an indexer by key, which can take indices as enumerated in the Keys property.
The typical approach to serializing collections is to handle them as a special case, treating each primitive collection type (array, List, Dictionary, etc.) individually.
Note that there is a difference here between a property returning IEnumerable and an indexer.

Is it possible to loop through a C# Class object?

I have a C# class that I want to loop through the properties as a key/value pair but don't know how.
Here is what I would like to do:
Foreach (classobjectproperty prop in classobjectname)
{
if (prop.name == "somename")
//do something cool with prop.value
}
Yup:
Type type = typeof(Form); // Or use Type.GetType, etc
foreach (PropertyInfo property in type.GetProperties())
{
// Do stuff with property
}
This won't give them as key/value pairs, but you can get all kinds of information from a PropertyInfo.
Note that this will only give public properties. For non-public ones, you'd want to use the overload which takes a BindingFlags. If you really want just name/value pairs for instance properties of a particular instance, you could do something like:
var query = foo.GetType()
.GetProperties(BindingFlags.Public |
BindingFlags.Instance)
// Ignore indexers for simplicity
.Where(prop => !prop.GetIndexParameters().Any())
.Select(prop => new { Name = prop.Name,
Value = prop.GetValue(foo, null) });
foreach (var pair in query)
{
Console.WriteLine("{0} = {1}", pair.Name, pair.Value);
}
Check out the solutions here - although that restricts to public properies the approach should work for you to get them all.

How to access contents of Object If I don't know the structure in c#?

I have an object and I don't know its structure until runtime. So is there any way to access data from the object ?
Thanks.
PS: I can't think of any other details to provide, please ask me if this isn't enough!
Well, you can do with with reflection. For example:
public static void ShowProperties(object o)
{
if (o == null)
{
Console.WriteLine("Null: no properties");
return;
}
Type type = o.GetType();
var properties = type.GetProperties(BindingFlags.Public
| BindingFlags.Instance);
// Potentially put more filtering in here
foreach (var property in properties.Where
(p => p.CanRead && p.GetIndexParameters().Length == 0))
{
Console.WriteLine("{0}: {1}", property.Name, property.GetValue(o, null));
}
}
Look at the Type API for ways to get methods, events, fields, nested types etc.
Have a look at Reflection
You can use reflection to determine what properties, methods, and fields an object has. take a look at the methods on the Type type

Categories