Linq Select dynamically from generic <T> List - c#

I want to get a list of each object from my List<T> (except strings, ints etc). And then Invoke (generic, recursive method with reflection). The problem is I am iterating on the property names, and have no idea how to select.
Error CS0021 Cannot apply indexing with [] to an expression of type 'T'
Code:
public static void My method<T>(IEnumerable<T> query)
{
var t = typeof(T);
var Headings = t.GetProperties();
for (int i = iteratorStart; i < Headings.Count(); i++)
{
if (IsValue(Headings[i].PropertyType.FullName))
{
}
else
{
Type type = Type.GetType(Headings[i].PropertyType.FullName);
var mi = typeof(ExcelExtension);
var met = mi.GetMethod("ListToExcel");
var genMet = met.MakeGenericMethod(type);
var nested = query.Select(p => p[Headings[i].Name]);
object[] parametersArray = new object[] { pck, nested, i };
genMet.Invoke(null, parametersArray);
}
}
}

As far as I can see, this is what you want:
public static void Mymethod<T>(IEnumerable<T> query)
{
var t = typeof(T);
int pck = 1234;
var mi = typeof(ExcelExtension);
var met = mi.GetMethod("ListToExcel");
var Headings = t.GetProperties();
for(int i=0; i < Headings.Length; ++i)
{
var prop = Headings[i];
if (prop.PropertyType.IsClass)
{
var genMet = met.MakeGenericMethod(prop.PropertyType);
var nested = query.Select(p => prop.GetValue(p));
object[] parametersArray = new object[] { pck, nested, i };
genMet.Invoke(null, parametersArray);
}
}
}
class ExcelExtension
{
public void ListToExcel<T>(int pck, IEnumerable<object> nested, int i)
{
}
}

Assuming you are using c# 6.0 or higher. You can use generic type parameters like;
public static void MyMethod<T>(IEnumerable<T> query) where T : IList
{
//Your code here
}
This way, you ensure that T is List of something and reaching indexing won't be a problem.
UPDATE
I misunderstood the question earlier. Here is the updated solution.
public static void MyMethod<T>(IEnumerable<T> query)
{
var t = typeof(T);
var Headings = t.GetProperties();
for (int i = iteratorStart; i < Headings.Count(); i++)
{
if (false == IsValue(Headings[i].PropertyType.FullName))
{
Type type = Type.GetType(Headings[i].PropertyType.FullName);
var mi = typeof(ExcelExtension);
var met = mi.GetMethod("ListToExcel");
var genMet = met.MakeGenericMethod(type);
//Assuming you want to get property value here. IF not You can use like Headings[i].GetName
var nested = query.Select(p =>Convert.ChangeType( Headings[i].GetValue(p),Headings[i].GetType()));
object[] parametersArray = new object[] { pck, nested, i };
genMet.Invoke(null, parametersArray);
}
}
}
Error Explanation:
The problem is in the Select(p => p[something here]) part. Since p is not the property list or array but a type of object, it doesn't contain any indexer. You should use reflection like above example.

Related

dynamic Array object

I'm trying to build a generic method to convert objects into ExpandoObjects and I can handle all cases except when one of the properties is an array.
public static ExpandoObject ToExpando(this object AnonymousObject) {
dynamic NewExpando = new ExpandoObject();
foreach (var Property in AnonymousObject.GetType().GetProperties()) {
dynamic Value;
if (IsPrimitive(Property.PropertyType)) {
Value = Property.GetValue(AnonymousObject);
} else if (Property.PropertyType.IsArray) {
dynamic ArrayProperty = new List<dynamic>();
var ArrayElements = (Array)Property.GetValue(AnonymousObject);
for (var i = 0; i < ArrayElements.Length; i++) {
var Element = ArrayElements.GetValue(i);
if (IsPrimitive(Element.GetType())) {
ArrayProperty.Add(Element);
} else {
ArrayProperty.Add(ToExpando(Element));
}
}
Value = ArrayProperty;//.ToArray();
} else {
Value = ToExpando(Property.GetValue(AnonymousObject));
}
((IDictionary<string, object>) NewExpando)[Property.Name] = Value;
}
return NewExpando;
}
private static bool IsPrimitive(System.Type type) {
while (type.IsGenericType && type.GetGenericTypeDefinition() == typeof (Nullable<>)) {
// nullable type, check if the nested type is simple.
type = type.GetGenericArguments()[0];
}
return type.IsPrimitive || type.IsEnum || type.Equals(typeof (string)) || type.Equals(typeof (decimal));
}
Any property that's an array doesn't seem to be a dynamic object and when I use it on something like a razor template the array elements and properties aren't visible.
For example, if I do this:
var EmailParams = new {
Parent = new {
Username = "User1",
},
Students = new [] {new {Username = "Student1", Password = "Pass1"} }
};
I get the following:
As you can see the anonymous object at the top has an array of Students, but the converted ExpandoObject does not.
Does anyone have any insight on how I would change the code to add support for arrays/list in the ExpandoObject?
Thanks!
When you create an object like
var person = new
{
FirstName = "Test",
LastName = new List<Person>() { new Person()
{
FirstName = "Tes2"
} }
};
LastName is a generic list and Property.PropertyType.IsArray returns false on that case. So your "array/list" is not treated with this logic that you are trying to add
dynamic ArrayProperty = new List<dynamic>();
var ArrayElements = (Array)Property.GetValue(AnonymousObject);
for (var i = 0; i < ArrayElements.Length; i++) {
var Element = ArrayElements.GetValue(i);
if (IsPrimitive(Element.GetType())) {
ArrayProperty.Add(Element);
} else {
ArrayProperty.Add(ToExpando(Element));
}
}
Hope this helps
Just one remark, you don't need to check again inside the logic of the if(Property.Property.Type.IsArray) the Primitive values, you did it, that is one of the stop conditions of your recursion. Below is the same code with the difference that I am mentioning
public static ExpandoObject ToExpando(this object AnonymousObject)
{
dynamic NewExpando = new ExpandoObject();
foreach (var Property in AnonymousObject.GetType().GetProperties())
{
dynamic Value;
if (IsPrimitive(Property.PropertyType))
{
Value = Property.GetValue(AnonymousObject);
}
else if (Property.PropertyType.IsArray)
{
var ArrayProperty = new List<ExpandoObject>();
var elements = Property.GetValue(AnonymousObject) as IEnumerable;
//is the same as foreach all elements calling to Expando and adding them to elemenstArray
if (elements != null)
ArrayProperty.AddRange(from object elem in elements select ToExpando(elem));
Value = ArrayProperty;
}
else
{
Value = ToExpando(Property.GetValue(AnonymousObject));
}
((IDictionary<string, object>)NewExpando)[Property.Name] = Value;
}
return NewExpando;
}

XML Serializing dapper results

I store SQL results into a dynamic List, of which has an underlying DapperRow type. I am trying to serialize/unserialize this List of which XMLserializer complains:
There was an error generating the XML document. ---> System.InvalidOperationException: To be XML serializable, types which inherit from IEnumerable must have an implementation of Add(System.Object) at all levels of their inheritance hierarchy. Dapper.SqlMapper+DapperRow does not implement Add(System.Object).
Is there a way around this (besides the obvious casting the results to my own concrete object), or is it possible to somehow make DapperRow objects conform to System.Xml.XMLserializer constraints?
It states my result array is System.Collections.Generic.List<dynamic> {System.Collections.Generic.List<object>}
Opening the array it says each object is of type object {Dapper.SqlMapper.DapperRow}
I think because DapperRows are now basically IDictionary<string, object> that XML is having issues (I cannot use anything but System.Xml.XmlSerializer so don't suggest an alternative).
I just want to be able to turn a List<dynamic> that I get from Dapper and serialize and deserialize correctly using System.Xml.XmlSerializer
I was able to get something at least serializable by using a serializable dictionary : http://weblogs.asp.net/pwelter34/444961
var results = conn.Query<dynamic>(sql, param);
var resultSet = new List<SerializableDictionary<string, object>>();
foreach (IDictionary<string, object> row in results)
{
var dict = new SerializableDictionary<string, object>();
foreach (var pair in row)
{
dict.Add(pair.Key, pair.Value);
}
resultSet.Add(dict);
}
Its ugly, so I hope more elegant solutions come up
is it possible to somehow make DapperRow objects conform to System.Xml.XMLserializer constraints?
I don't think that this is possible. The DapperRow class is private and it does not have a parameterless constructor.
However, you might be able to use other means to fix your problem.
I suggest that you put your complex serialization logic behind an extension method. This way your original code will stay clean.
I also suggest the following model for storing the data (although you can choose not to use it and use your own logic behind the extension method):
public class Cell
{
public string Name { get; set; }
public object Value { get; set; }
public Cell(){}
public Cell(KeyValuePair<string, object> kvp)
{
Name = kvp.Key;
Value = kvp.Value;
}
}
public class Row : List<Cell>
{
public Row(){}
public Row(IEnumerable<Cell> cells)
: base(cells){}
}
public class Rows : List<Row>
{
public Rows(){}
public Rows(IEnumerable<Row> rows )
:base(rows){}
}
And then the extension method should look something like this:
public static class Extensions
{
public static void Serialize(this IEnumerable<dynamic> enumerable, Stream stream)
{
var rows =
new Rows(
enumerable
.Cast<IEnumerable<KeyValuePair<string, object>>>()
.Select(row =>
new Row(row.Select(cell => new Cell(cell)))));
XmlSerializer serializer = new XmlSerializer(typeof(Rows));
serializer.Serialize(stream, rows);
}
}
Then you would be able to use this code:
var result = connection.Query("SELECT * From Customers");
var memory_stream = new MemoryStream();
result.Serialize(memory_stream);
See how this code is very small because all the complex logic is moved to the extension method.
The model that I suggested allows also for deserialization, just make sure that you use the correct type (e.g. Rows) like this:
XmlSerializer serializer = new XmlSerializer(typeof(Rows));
Rows rows = (Rows)serializer.Deserialize(stream);
You can also have an extension method that just converts the resultset of Dapper to the Rows type and handle the serialization of Rows your self. Such extension method should look something like this:
public static Rows ToRows(this IEnumerable<dynamic> enumerable)
{
return
new Rows(
enumerable
.Cast<IEnumerable<KeyValuePair<string, object>>>()
.Select(row =>
new Row(row.Select(cell => new Cell(cell)))));
}
And then use it like this:
var rows = connection.Query("SELECT * From Customers").ToRows();
XmlSerializer serializer = new XmlSerializer(typeof(Rows));
serializer.Serialize(stream, rows);
Firstly, decorate the DapperResultSet with a [Serializable] attribute. Also create a constructor and in that, assign Rows with an empty List<object>. Use this modified code: (it also contains a modified implemented method)
[Serializable]
public class DapperResultSet : IEnumerable<object>
{
public List Rows { get; set; }
public void Add(dynamic o)
{
Rows.Add(o);
}
public DapperResultSet()
{
Rows = new List<object>();
}
public IEnumerator<object> GetEnumerator()
{
return Rows.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Next in your event handler (or where you would like to do the serialization):
var results = conn.Query<dynamic>(sql, param);
var r = new DapperResultSet();
foreach (var row in results)
{
r.Add(row);
}
//Here is the serialization part:
XmlSerializer xs = new XmlSerializer(typeof(DapperResultSet));
xs.Serialize(new FileStream("Serialized.xml", FileMode.Create), r); //Change path if necessary
For Deserialization,
XmlSerializer xs = new XmlSerializer(typeof(DapperResultSet));
DapperResultSet d_DRS = xs.Deserialize(new FileStream("Serialized.xml", FileMode.Open)); //Deserialized
Challenging request, because Dapper is not designed to be serializable. But let see what can be done.
The first decision is easy - we need to implement IXmlSerializable. The question is how.
Serialization is not a big deal, since we have the field names and values. So we could use similar approach to the SerializableDictionary<TKey, TValue> you mentioned. However, it heavily relies on typeof(TKey) and typeof(TValue)'. We have no problem with key (it's a string), but the type of the value is object. As I mentioned, it's not a problem to write an object value as XML. The problem is deserialization. At that point, all we have is a string and no any clue what that string is. Which means we need to store some metadata in order to be able to deserialize correctly. Of course there are many ways to do that, but I decided to store field names and types separately at the beginning, and then items with values only.
Putting it all together, here is what I ended up:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace Samples
{
public class DapperResultSet : IXmlSerializable
{
static readonly Type TableType;
static readonly Type RowType;
static readonly Func<object, string[]> GetFieldNames;
static readonly Func<object, object[]> GetFieldValues;
static readonly Func<string[], object> CreateTable;
static readonly Func<object, object[], object> CreateRow;
static DapperResultSet()
{
TableType = typeof(Dapper.SqlMapper).GetNestedType("DapperTable", BindingFlags.NonPublic);
RowType = typeof(Dapper.SqlMapper).GetNestedType("DapperRow", BindingFlags.NonPublic);
// string[] GetFieldNames(object row)
{
var row = Expression.Parameter(typeof(object), "row");
var expr = Expression.Lambda<Func<object, string[]>>(
Expression.Field(Expression.Field(Expression.Convert(row, RowType), "table"), "fieldNames"),
row);
GetFieldNames = expr.Compile();
}
// object[] GetFieldValues(object row)
{
var row = Expression.Parameter(typeof(object), "row");
var expr = Expression.Lambda<Func<object, object[]>>(
Expression.Field(Expression.Convert(row, RowType), "values"),
row);
GetFieldValues = expr.Compile();
}
// object CreateTable(string[] fieldNames)
{
var fieldNames = Expression.Parameter(typeof(string[]), "fieldNames");
var expr = Expression.Lambda<Func<string[], object>>(
Expression.New(TableType.GetConstructor(new[] { typeof(string[]) }), fieldNames),
fieldNames);
CreateTable = expr.Compile();
}
// object CreateRow(object table, object[] values)
{
var table = Expression.Parameter(typeof(object), "table");
var values = Expression.Parameter(typeof(object[]), "values");
var expr = Expression.Lambda<Func<object, object[], object>>(
Expression.New(RowType.GetConstructor(new[] { TableType, typeof(object[]) }),
Expression.Convert(table, TableType), values),
table, values);
CreateRow = expr.Compile();
}
}
static readonly dynamic[] emptyItems = new dynamic[0];
public IReadOnlyList<dynamic> Items { get; private set; }
public DapperResultSet()
{
Items = emptyItems;
}
public DapperResultSet(IEnumerable<dynamic> source)
{
if (source == null) throw new ArgumentNullException("source");
Items = source as IReadOnlyList<dynamic> ?? new List<dynamic>(source);
}
XmlSchema IXmlSerializable.GetSchema() { return null; }
void IXmlSerializable.WriteXml(XmlWriter writer)
{
if (Items.Count == 0) return;
// Determine field names and types
var fieldNames = GetFieldNames((object)Items[0]);
var fieldTypes = new TypeCode[fieldNames.Length];
for (int count = 0, i = 0; i < Items.Count; i++)
{
var values = GetFieldValues((object)Items[i]);
for (int c = 0; c < fieldTypes.Length; c++)
{
if (fieldTypes[i] == TypeCode.Empty && values[c] != null)
{
fieldTypes[i] = Type.GetTypeCode(values[c].GetType());
if (++count >= fieldTypes.Length) break;
}
}
}
// Write fields
writer.WriteStartElement("Fields");
writer.WriteAttributeString("Count", XmlConvert.ToString(fieldNames.Length));
for (int i = 0; i < fieldNames.Length; i++)
{
writer.WriteStartElement("Field");
writer.WriteAttributeString("Name", fieldNames[i]);
writer.WriteAttributeString("Type", XmlConvert.ToString((int)fieldTypes[i]));
writer.WriteEndElement();
}
writer.WriteEndElement();
// Write items
writer.WriteStartElement("Items");
writer.WriteAttributeString("Count", XmlConvert.ToString(Items.Count));
foreach (IDictionary<string, object> item in Items)
{
writer.WriteStartElement("Item");
foreach (var entry in item)
{
writer.WriteStartAttribute(entry.Key);
writer.WriteValue(entry.Value);
writer.WriteEndAttribute();
}
writer.WriteEndElement();
}
writer.WriteEndElement();
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
reader.MoveToContent();
bool isEmptyElement = reader.IsEmptyElement;
reader.ReadStartElement(); // Container
if (isEmptyElement) return;
// Read fields
int fieldCount = XmlConvert.ToInt32(reader.GetAttribute("Count"));
reader.ReadStartElement("Fields");
var fieldNames = new string[fieldCount];
var fieldTypes = new TypeCode[fieldCount];
var fieldIndexByName = new Dictionary<string, int>(fieldCount);
for (int c = 0; c < fieldCount; c++)
{
fieldNames[c] = reader.GetAttribute("Name");
fieldTypes[c] = (TypeCode)XmlConvert.ToInt32(reader.GetAttribute("Type"));
fieldIndexByName.Add(fieldNames[c], c);
reader.ReadStartElement("Field");
}
reader.ReadEndElement();
// Read items
int itemCount = XmlConvert.ToInt32(reader.GetAttribute("Count"));
reader.ReadStartElement("Items");
var items = new List<dynamic>(itemCount);
var table = CreateTable(fieldNames);
for (int i = 0; i < itemCount; i++)
{
var values = new object[fieldCount];
if (reader.MoveToFirstAttribute())
{
do
{
var fieldName = reader.Name;
var fieldIndex = fieldIndexByName[fieldName];
values[fieldIndex] = Convert.ChangeType(reader.Value, fieldTypes[fieldIndex], CultureInfo.InvariantCulture);
}
while (reader.MoveToNextAttribute());
}
reader.ReadStartElement("Item");
var item = CreateRow(table, values);
items.Add(item);
}
reader.ReadEndElement(); // Items
reader.ReadEndElement(); // Container
Items = items;
}
}
}
Some things would have been easier if we modify the Dapper source code, but I assume you don't want to do that.
And here is a sample usage:
static void Test(IEnumerable<dynamic> source)
{
var stream = new MemoryStream();
var sourceSet = new DapperResultSet(source);
var serializer = new XmlSerializer(typeof(DapperResultSet));
serializer.Serialize(stream, sourceSet);
stream.Position = 0;
var reader = new StreamReader(stream);
var xml = reader.ReadToEnd();
stream.Position = 0;
var deserializer = new XmlSerializer(typeof(DapperResultSet));
var target = ((DapperResultSet)deserializer.Deserialize(stream)).Items;
}

Convert IList to SomeType[]

I have a collection of type List that I want to convert to SomeType[]. SomeType is not known before runtime.
This must be done with the signature of the following procedure.
private object ConvertListToArray(IList collection)
{
// This does not work since SomeType is not known before runtime.
var convertedList = collection.Cast<SomeType>().ToArray();
return convertedList;
}
Notice that collection is IList, but it is known that the concrete type is
List<SomeType>
The return collection must be an object of type SomeType[].
How can this be done?
public static class ListExtensions
{
public static T[] ConvertToArray<T>(IList list)
{
return list.Cast<T>().ToArray();
}
public static object[] ConvertToArrayRuntime(IList list, Type elementType)
{
var convertMethod = typeof(ListExtensions).GetMethod("ConvertToArray", BindingFlags.Static | BindingFlags.Public, null, new [] { typeof(IList)}, null);
var genericMethod = convertMethod.MakeGenericMethod(elementType);
return (object[])genericMethod.Invoke(null, new object[] {list});
}
}
[TestFixture]
public class ExtensionTest
{
[Test]
public void TestThing()
{
IList list = new List<string>();
list.Add("hello");
list.Add("world");
var myArray = ListExtensions.ConvertToArrayRuntime(list, typeof (string));
Assert.IsTrue(myArray is string[]);
}
}
You can do this with the implementation below:
class Program
{
static void Main(string[] args)
{
// same type
var myCollection = new List<string> {"Hello", "World"};
var array = (string[])myCollection.ConvertToArray();
Console.WriteLine(array[0]);
// new type
var intList = new List<int> {1, 2, 3};
var stringArray = (string[])intList.ConvertToArray(typeof(string));
Console.WriteLine(stringArray[0]);
// mixed types
var ouch = new List<object> {1, "Mamma", 3.0};
var result= (string[])ouch.ConvertToArray(typeof(string));
Console.WriteLine(result[0]);
}
}
The implementation:
public static class ListExtensions
{
public static object ConvertToArray(this IList collection)
{
// guess type
Type type;
if (collection.GetType().IsGenericType && collection.GetType().GetGenericArguments().Length == 0)
type = collection.GetType().GetGenericArguments()[0];
else if (collection.Count > 0)
type = collection[0].GetType();
else
throw new NotSupportedException("Failed to identify collection type for: " + collection.GetType());
var array = (object[])Array.CreateInstance(type, collection.Count);
for (int i = 0; i < array.Length; ++i)
array[i] = collection[i];
return array;
}
public static object ConvertToArray(this IList collection, Type arrayType)
{
var array = (object[])Array.CreateInstance(arrayType, collection.Count);
for (int i = 0; i < array.Length; ++i)
{
var obj = collection[i];
// if it's not castable, try to convert it
if (!arrayType.IsInstanceOfType(obj))
obj = Convert.ChangeType(obj, arrayType);
array[i] = obj;
}
return array;
}
}
You could try:
private T[] ConvertListToArray<T>(IList collection)
{
// This does not work since SomeType is not known before runtime.
var convertedList = collection.Cast<T>().ToArray();
return convertedList;
}
I'm posting this as the other answers seem a bit verbose. I'm pretty sure this does what the OP is looking for. It worked for me.
public static Array ConvertToArray(ICollection collection, Type type)
{
var array = Array.CreateInstance(type, collection.Count);
collection.CopyTo(array, 0);
return array;
}

Make a class specific generic method

I have created this Update method
public void Update(Person updated)
{
var oldProperties = GetType().GetProperties();
var newProperties = updated.GetType().GetProperties();
for (var i = 0; i < oldProperties.Length; i++)
{
var oldval = oldProperties[i].GetValue(this, null);
var newval = newProperties[i].GetValue(updated, null);
if (oldval != newval)
oldProperties[i].SetValue(this, newval, null);
}
}
What it does is comparing two Person objects and if there is any new values. It updates the original object. This works great, but being a lazy programmer, I would like it to be more reusable.
I would like it to work like this.
Person p1 = new Person(){Name = "John"};
Person p2 = new Person(){Name = "Johnny"};
p1.Update(p2);
p1.Name => "Johnny"
Car c1 = new Car(){Brand = "Peugeot"};
Car c2 = new Car(){Brand = "BMW"};
c1.Update(c2);
c1.Brand => "BMW"
c1.Update(p1); //This should not be possible and result in an error.
I was thinking about using and Abstract Class to hold the Method and then use some Generic, but I don't know how to make it Class specific.
public static void Update(object original, object updated)
{
var oldProperties = original.GetType().GetProperties();
var newProperties = updated.GetType().GetProperties();
for (var i = 0; i < oldProperties.Length; i++)
{
var oldval = oldProperties[i].GetValue(original, null);
var newval = newProperties[i].GetValue(updated, null);
if (!Equals(oldval,newval))
oldProperties[i].SetValue(original, newval, null);
}
}
or if you want to ensure the same type:
public static void Update<T>(T original, T updated)
{
var properties = typeof(T).GetProperties();
for (var i = 0; i < properties.Length; i++)
{
var oldval = properties[i].GetValue(original, null);
var newval = properties[i].GetValue(updated, null);
if (!Equals(oldval,newval))
properties[i].SetValue(original, newval, null);
}
}
Your code has a little flaw, in the fact that if you don't enforce that the two objects are effectively of the exact same type, they may not have the same properties and you would face errors.
A generic method like this should operate correctly on almost anything, as long as it's a class (that's what the constraint where T: class is there for: if it's not a class you're passing, code won't compile).
static void Update<T>(T original, T updated) where T : class
{
var Properties = typeof(T).GetProperties();
foreach (PropertyInfo property in Properties)
{
var oldval = property.GetValue(original, null);
var newval = property.GetValue(updated, null);
if (oldval != newval) property.SetValue(original, newval, null);
}
}
Try this pattern:
interface IUpdateable
{
void Update(IUpdateable updated)
}
public void Update<T>(T updated) where T:IUpdateable
{
...
...
}

PropertyInfo.GetValue() - how do you index into a generic parameter using reflection in C#?

This (shortened) code..
for (int i = 0; i < count; i++)
{
object obj = propertyInfo.GetValue(Tcurrent, new object[] { i });
}
.. is throwing a 'TargetParameterCountException : Parameter count mismatch' exception.
The underlying type of 'propertyInfo' is a Collection of some T. 'count' is the number of items in the collection. I need to iterate through the collection and perform an operation on obj.
Advice appreciated.
Reflection only works on one level at a time.
You're trying to index into the property, that's wrong.
Instead, read the value of the property, and the object you get back, that's the object you need to index into.
Here's an example:
using System;
using System.Collections.Generic;
using System.Reflection;
namespace DemoApp
{
public class TestClass
{
public List<Int32> Values { get; private set; }
public TestClass()
{
Values = new List<Int32>();
Values.Add(10);
}
}
class Program
{
static void Main()
{
TestClass tc = new TestClass();
PropertyInfo pi1 = tc.GetType().GetProperty("Values");
Object collection = pi1.GetValue(tc, null);
// note that there's no checking here that the object really
// is a collection and thus really has the attribute
String indexerName = ((DefaultMemberAttribute)collection.GetType()
.GetCustomAttributes(typeof(DefaultMemberAttribute),
true)[0]).MemberName;
PropertyInfo pi2 = collection.GetType().GetProperty(indexerName);
Object value = pi2.GetValue(collection, new Object[] { 0 });
Console.Out.WriteLine("tc.Values[0]: " + value);
Console.In.ReadLine();
}
}
}
I was most of the way there until I saw this, and I am posting this because I didn't see it anywhere else; the key was using GetValue(collection, new Object[] { i }); in the loop rather than trying to use GetValue(collection, new Object[i]); outside the loop.
(You can probably ignore the "output" in my example);
private static string Recursive(object o)
{
string output="";
Type t = o.GetType();
if (t.GetProperty("Item") != null)
{
System.Reflection.PropertyInfo p = t.GetProperty("Item");
int count = -1;
if (t.GetProperty("Count") != null &&
t.GetProperty("Count").PropertyType == typeof(System.Int32))
{
count = (int)t.GetProperty("Count").GetValue(o, null);
}
if (count > 0)
{
object[] index = new object[count];
for (int i = 0; i < count; i++)
{
object val = p.GetValue(o, new object[] { i });
output += RecursiveWorker(val, p, t);
}
}
}
return output;
}
Assembly zip_assembly = Assembly.LoadFrom(#"C:\Ionic.Zip.Reduced.dll");
Type ZipFileType = zip_assembly.GetType("Ionic.Zip.ZipFile");
Type ZipEntryType = zip_assembly.GetType("Ionic.Zip.ZipEntry");
string local_zip_file = #"C:\zipfile.zip";
object zip_file = ZipFileType.GetMethod("Read", new Type[] { typeof(string) }).Invoke(null, new object[] { local_zip_file });
// Entries is ICollection<ZipEntry>
IEnumerable entries = (IEnumerable)ZipFileType.GetProperty("Entries").GetValue(zip_file, null);
foreach (object entry in entries)
{
string file_name = (string)ZipEntryType.GetProperty("FileName").GetValue(entry, null);
Console.WriteLine(file_name);
}

Categories