Reflecting a List<T> - c#

I'm trying to make a model reflection tool. I have come a long way so far but now i'm stuck.
I have this
public static void RenderModelList(List<T> modelList)
{
foreach (T model in modelList)
{
PropertyInfo[] properties = model.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
object propValue = property.GetValue(model, null);
//Check if the property is a collection and do recursion
if (propValue != null)
{
if (isCollection(propValue))
{
//This only works for Lists of the same <T>
List<T> li = Convert.ChangeType(propValue, propValue.GetType()) as List<T>;
if (li != null)
{
if (li.Count > 0)
{
RenderModelList(li, loop);
}
}
else
{
//Its another type what to do?
// Create a List<> of unknown type??
}
}
}
}
}
}
My problem is that if I pass this method a List<Persons> and the Person has a property which is a List<Cars> - I can't use Convert.ChangeType - because this is not the T.
So how do I loop thrugh a "List" and get access to the properties of this object ?

It seems to me that your method can be a lot more loosely typed:
public static void RenderModelList(IEnumerable list)
{
foreach (object model in list)
{
...
}
}
Then you just need to cast to IEnumerable, not a specific sequence or list type.

Well, your method should not rely on T. You can just use IEnumerable instead (not IEnumerable<T>, because it again depends on T). Note that every List<T> implements IEnumerable, so your method will work with them; however, other collections often implement IEnumerable as well. This may or may not be what you need.
If you choose the proposed way, your test isCollection will be like this:
IEnumerable propValueAsEnumerable = propValue as IEnumerable;
if (propValueAsEnumerable != null)
RenderModelList(propValueAsEnumerable);
I would perhaps refactor your method into something like that:
IEnumerable<object> GetPropertyValuesFlat(object o)
{
return o.GetType()
.GetProperties()
.Select(pi => pi.GetValue(o, null))
.Where(pv => pv != null)
.SelectMany(pv => pv is IEnumerable<object> ?
(IEnumerable<object>)pv : new[] {pv});
}
//...
foreach (object p in GetPropertyValuesFlat(o))
render(p);
(caution: not tested)
Edit: well, won't work as SelectMany doesn't understand non-generic IEnumerable. Changed it to work with IEnumerable<object>. At least, each IEnumerable<T> is an IEnumerable<object> with class T.

Related

How to get the underlying type of an IList item?

I have a method that receives an IList. Is there a way to get the Type of the items of the IList?
public void MyMethod(IList myList)
{
}
I have a class that you can associate an IList to, you can also add a NewItem function, but I would like to be able to add items with the default empty constructor in case the user didn't set the NewItem function.
How can I get the Type of the underlying items? I would know how to do it if it was an IList<T>, but I can't change the API, because I can receive any kind of collection that implements IList, only restriction, that is not enforced by code, is that all the items in the collections we receive are of the same type.
Since it's an IList, you'd first have to check if it is actually generic:
if (list.GetType().IsGenericType)
Console.WriteLine($"Is generic collection of {list.GetType().GenericTypeArguments[0]}");
else
Console.WriteLine("Is not generic");
For example, using
IList list = new List<string>();
would give Is generic collection of System.String, and
IList list = new ArrayList();
would give Is not generic
Here is a heuristic algorithm that you can start with. Most (if not all) generic list interfaces inherit from IEnumerable<T>, so you can check if the list implements IEnumerable<T>. If it doesn't, you check the type of the first element, assuming of course that the list would contain elements of the same type. If the list is empty, this method returns null.
public static Type HeuristicallyDetermineType(IList myList)
{
var enumerable_type =
myList.GetType()
.GetInterfaces()
.Where(i => i.IsGenericType && i.GenericTypeArguments.Length == 1)
.FirstOrDefault(i => i.GetGenericTypeDefinition() == typeof (IEnumerable<>));
if (enumerable_type != null)
return enumerable_type.GenericTypeArguments[0];
if (myList.Count == 0)
return null;
return myList[0].GetType();
}
you could do something like this have a List and in an additional List add the types.
IList<object> iListObj = new List<object>
{
1234,
"Harold Nelson",
false,
'A'
};
var typeList = new List<object>();
foreach (var item in iListObj)
{
typeList.Add(item.GetType());
}
If the types passed are non-generic than you may have a grabbag of types (e.g.):
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
namespace ConsoleApp1
{
public class Program
{
public static void Main(string[] args)
{
var list = new ArrayList();
list.Add("Hello");
list.Add(0);
var types = GetTypes(list);
foreach (var itemType in types)
{
Console.WriteLine(itemType.ToString());
}
Console.ReadLine();
}
public static HashSet<Type> GetTypes(IList list)
{
var types = new HashSet<Type>();
foreach (var item in list)
{
var newType = item.GetType();
if (!types.Contains(newType))
{
types.Add(newType);
}
}
return types;
}
}
}
You would have to make the GetTypes function more complicated to deal with the generic case also.

Cast type as specific MyCollection<MyType> and call method on it via Reflection

I have a custom collection type ObservableStateCollection that, for simplistic purposes, looks like:
public class ObservableStateCollection<T> : IList<T>, INotifyCollectionChanged, INotifyPropertyChanged where T : StateObservable
{
private List<T> _items;
private List<T> _deleted;
public IEnumerator<T> GetEnumerator()
{
return _items.GetEnumerator();
}
public IEnumerable<StateObservable> GetAll()
{
return _items.Concat(_deleted);
}
//...
}
Note that type T must be derived from StateObservable.
Now, I'm neck deep in reflection. I'll spare the details of 'why', and just show you where I'm currently at. I need to check if a particular property on my model is an ObservableStateCollection<T> and use a foreach to loop through the GetAll() method.
Currently, I'm at:
if(prop.PropertyType.GetGenericTypeDefinition() == typeof(ObservableStateCollection<>))
{
var collection = (ObservableStateCollection<StateObservable>)prop.GetValue(model, null);
foreach (var e in collection.GetAll())
{
//act on ObservableStateCollection<StateObservable>
}
}
which throws an exception on the line var collection = ..., as I can't cast ObservableStateCollection<DerivedType> to ObservableStateCollection<BaseType>
What are my options here? How to I get a strongly typed object back that I can call GetAll on?
ahh got it. I invoked GetAll via reflection. Not sure why that didn't occur to me at first:
if(prop.PropertyType.GetGenericTypeDefinition() == typeof(ObservableStateCollection<>))
{
MethodInfo m = prop.PropertyType.GetMethod("GetAll");
var collection = m.Invoke(prop.GetValue(model, null), null);
foreach (var e in (IEnumerable)collection)
{
\\act on item in collection
}
}

Cast collection of objects to collection of concrete class

Consider, that I've the following method:
public T Resolve<T>()
{
var targetType = typeof(T);
if (targetType.IsGenericType
&& targetType.GetGenerictTypeDefinition() == typeof(IEnumerable<>))
{
List<object> collection = this.ResolveCollection(targetType);
return (T)(object)collection;
}
return (T)this.ResolveSingle(targetType);
}
Sample usage:
IEnumerable<IFoo> coll = myClass.Resolve<IEnumerable<IFoo>>();
It is obvious, that sample will throw exception of invalid cast, because of covariance - we cannot cast List<object> into IEnumerable<IFoo> despite collection contains implementations of IFoo only.
Is there any workaround for that problem when using reflection and non-generic methods? I don't want to change Resolve signature so I don't have generic type of item to use LINQ Cast.
It is going to be ugly. You can also call the Linq method Enumerable.Cast<> after "making" it, i.e. filling out the generic argument.
Here is an extension method:
public static TIEnumerable ToIEnumerable<TIEnumerable>(this IEnumerable<object> source)
{
var type = typeof(TIEnumerable);
if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(IEnumerable<>))
throw new ArgumentException("Wrong type arg: " + type, "TIEnumerable");
var methOpen = typeof(Enumerable).GetMethod("Cast");
var methConstructed = methOpen.MakeGenericMethod(type.GenericTypeArguments[0]);
return (TIEnumerable)methConstructed.Invoke(null, new object[] { source, });
}
(You could even extend the non-generic IEnumerable since Cast<> operates on that.)
Then the body of your if (in your question) could be:
List<object> collection = this.ResolveCollection(targetType);
return collection.ToIEnumerable<T>();
If you want eager iteration and returning a List<>, that is:
List<object> collection = this.ResolveCollection(targetType);
return collection.ToIEnumerable<T>()
.ToList();
Found workaround:
List<object> collection = this.ResolveCollection(targetType);
var itemType = targetType.GetGenericArguments()[0];
var listType = typeof(List<>).MakeGenericType(itemType);
var listInstance = Activator.CreateInstance(listType, new object[0]) as IList;
foreach (var instance in collection)
{
listInstance.Add(instance);
}
return (T)listInstance;
Then, casting works like a chram.

Retrieve Properties of HashSet passed as Object

I'm maintaining part of an application which takes an entity framework entity as an object and returns a list of all its properties and corresponding values.
The method looks something like this:
public static List<string> ExtractAttributes(object data)
{
List<string> attributes = new List<string>();
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(data))
{
Console.WriteLine("Name: {0} Value: {1} Type: {2}", property.Name, property.GetValue(data), data.GetType());
attributes.Add(property.Name);
}
return attributes;
}
On calling this method with the following object:
ExtractAttributes(HashSet<Dog> dogs);
The method returns Count and Comparer (properties of HashSet) rather than properties of Dog. Therefore it is necessary to convert the data object to the first object of the hashset collection (it only needs to be the first object in the set for reasons too long to explain in detail).
The code I've written to do this is as follows:
public static List<String> ExtractAttributes(object data)
{
...
if (data.GetType().IsGenericType &&
data.GetType().GetGenericTypeDefinition() == typeof(HashSet<>))
{
List<object> hashSetAsList = new List<object>((IEnumerable<object>)data);
if (hashSetAsList.Count > 0)
{
data = hashSetAsList[0];
}
}
...
}
Is there any way to improve this ugly-looking code given the constraint of being unable to alter anything else in the method/return type?
Edit
The ExtractAttributes method is recursive (in a way which depends on the logic of some external XML) but can be represented as:
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(data))
{
Console.WriteLine("Name: {0} Value: {1} Type: {2}", property.Name, property.GetValue(data), data.GetType());
if (property.GetValue(data).GetType().IsGenericType)
{
attributes.AddRange(ExtractAttributes(property.GetValue(data)));
}
}
You can check it more generally trying to cast the object as a simple non-generic IEnumerable.
public static List<String> ExtractAttributes(object data) {
...
if (data is IEnumerable) {
var enumerator = ((IEnumerable)data).GetEnumerator();
if (enumerator.MoveNext() && enumerator.Current != null) {
data = enumerator.Current;
}
}
...
}
And if you really only want this to be done for hash-set's, I guess you can add your criteria to check to hash-set's:
if (data is IEnumerable &&
data.GetType().IsGenericType &&
data.GetType().GetGenericTypeDefinition() == typeof(HashSet<>)) {
You might consider creating an overload of ExtractAttributes method that accepts, say, IEnumerable<object>. This way, a more specific overload will be chosen when you pass HashSet.
I guess you could just cast the HashSet and take the first Element
HashSet<object> castedObject = Object as HashSet<object>;
object first = castedObject != null ? castedObject.First() : "";

Get implementer class instance from the interface instance

I have some classes implementing an interface:
class FirstImplementer : IInterface { ... }
class AnotherImplementer : IInterface { ... }
Somewhere in the code, I got a list of instances of IInterface.
List<IInterface> MyList;
I want to know for each IInterface instance what is the implementer class of that specific instance (FirstImplementer or AnotherImplementer).
You can just use .GetType() on the instances in MyList and go from there.
MyList[0].GetType() > This is the same as typeof(FirstImplementer) et al.
foreach (var item in MyList)
{
var theType = item.GetType();
// why did you want theType, again?
// you generally shouldn't be concerned with how your interface is implemented
}
This alternative may be more useful, depending on what you're trying to do:
foreach (var item in MyList)
{
if (item is FirstImplementer)
{
var firstImpl = (FirstImplementer)item;
// do something with firstImpl
}
else if (item is AnotherImplementer)
{
var anotherImpl = (AnotherImplementer)item;
// do something with anotherImpl
}
}
It's generally better to use is or as over reflection (e.g. GetType) when it might make sense to do so.
foreach (var instance in MyList)
{
Type implementation = instance.GetType ();
}
If you need to get the first type argument if any, and null if no such type argument even exists for each instance in your list, which at design time you syntactically observe as interface references then you could use the GetGenericArguments method of the Type type.
Here's a little helper method which takes a bunch of objects
which may be null, but which would surely implement your interface if they weren't
(they would have a runtime type that did) and yields a bunch of types which
represent (in respective order) the discovered type arguments in a SomeImplementer pattern:
public IEnumerable<Type> GetTypeArgumentsFrom(IEnumerable<IInterface> objects) {
foreach (var obj in objects) {
if (null == obj) {
yield return null; // just a convention
// you return null if the object was null
continue;
}
var type = obj.GetType();
if (!type.IsGenericType) {
yield return null;
continue;
}
yield return type.GetGenericArguments()[0];
}
}

Categories

Resources