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() : "";
Related
I'm working on a script that I can feed all sorts of objects and tell it to modify them in all sorts of ways. This works really well with many types, but I don't know how to go about handling Lists (and other Collections).
This doesn't work:
List<Transform> transformList = new List<Transform>();
void MyFunction( object o ) {
if( o.GetType() == typeof( int ) DoIntStuff(); //Easy
else if( o.GetType() == typeof( Color ) DoColorStuff(); //Also Easy
else if( o.GetType() == typeof( List<> ) ) DoListStuff(); //Not as easy :(
}
void Start() {
MyFunction( transformList );
}
I can't do
typeof( List<T> )
because T doesn't exist in there of course.
typeof( List<object> )
does not work either.
So how can I find out if what I have is a list of any kind?
Why don't you test if object implements "IEnumerable" then check the type inside the collection(For genric collections) like the following:
IEnumerable tempList = o as IEnumerable;
if (tempList != null)
{
IEnumerable<DumyClass> items = tempList.OfType<DumyClass>();
if(items.Count() != 0)
{
//Then handle the list of the specific type as needed.
}
}
If only List type is needed check for "IList" like the following :
IList tempList = o as IList;
if (tempList != null)
{
//Then handle the list as needed.
}
Hope this was useful.
If you just want to check if it's an List I would go with #Ali Ezzat Odeh approach and just check for
else if(o is IList) DoListStuff();
If you need to find out the containing type to handle lists with different elements you could try this extension method:
public static class TypeExtensions
{
/// <summary>
/// Gets the inner type of the given type, if it is an Array or has generic arguments.
/// Otherwise NULL.
/// </summary>
public static Type GetInnerListType(this Type source)
{
Type innerType = null;
if (source.IsArray)
{
innerType = source.GetElementType();
}
else if (source.GetGenericArguments().Any())
{
innerType = source.GetGenericArguments()[0];
}
return innerType;
}
}
You can use it like
else if(o.GetType().GetInnerListType() == typeof(Transform)) DoTransformListStuff();
The extension handles all kinds of lists, also arrays. In my opinion it would be better not to only look for types of List. This way you could replace your list with an alternate container like with an array for performance or with an observable collection if you're going into the UI. It will leave you with more design choices in the future and you can better adapt.
As far as I know, there's no way to get the "List" part of a list object from the Type object. This is because according to the code, there is no "List" type, but actually a number of different generated types that correspond to every kind of generic that "List" receives throughout the course of your program. For example, if your program contained the following:
var a = new List<bool>();
var b = new List<int>();
var c = new List<char>();
Your compiled program will essentially contain the following classes, each with the T in its code replaced with the corresponding type:
List`1 // bool List
List`2 // int List
List`3 // char List
However, you could trick it a bit. Since the generic name will always follow the above pattern, you can use the name to perform your check:
var list = new List<int>();
var type = list.GetType();
bool isList = list.GetType().Name.Split('`')[0] == "List";
Assuming you want to check that your type is/inherits from a List<T> for some T (rather than just implements IList<T> for some T) you could use the following extension method:
public static class TypeExtensions
{
public static bool IsGenericList(this Type type)
{
return type.GetGenericListItemType() != null;
}
public static Type GetGenericListItemType(this Type type)
{
while (type != null)
{
if (type.IsGenericType)
{
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(List<>))
return type.GetGenericArguments()[0];
}
type = type.BaseType;
}
return null;
}
}
I have following generic method in C# that parses client data from an ASP.NET web forms application into some defined type:
public static T ParseClientRequest <T> (object data)
{
var t = (System.Collections.Generic.Dictionary<string,object>) data;
T obj = (T)Activator.CreateInstance(typeof(T));
foreach(var pair in t) {
FieldInfo field = obj.GetType().GetField(pair.Key);
field.SetValue(obj, pair.Value);
}
return obj;
}
I have two questions about it:
Is there any efficient way(using LINQ or other) of doing it without using loop? Or is it efficient enough?
The code throws exception if one of the type's field is of type other than string. How the object type can be parsed to a dynamically supplied type?
1- Efficiency is relative. Hard to answer. If it is good enough for you, then no problem
2- You can fix your code by using Convert.ChangeType
public static T ParseClientRequest<T>(object data)
{
var t = (System.Collections.Generic.Dictionary<string, object>)data;
T obj = (T)Activator.CreateInstance(typeof(T));
foreach (var pair in t)
{
FieldInfo field = obj.GetType().GetField(pair.Key);
field.SetValue(obj, Convert.ChangeType(pair.Value, field.FieldType)); //See this line
}
return obj;
}
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.
Say I define a variable like this:
var o = new { RBI = 108, Name = "Roberto Alomar" };
I can do something like:
Console.WriteLine("{0}", o);
But if I try:
foreach (var i in o) {
Console.WriteLine("{0}", o[i]);
}
I get an error:
foreach statement cannot operate on variables of type 'AnonymousType#1' because 'AnonymousType#1' does not contain a public definition for 'GetEnumerator'
So how does it work under the hood? I'd think that a method for turning an object into a string would have to loop through all the properties to accomplish the task. Is there some special method that allows this to happen, or am I misunderstanding how this works?
How does it work under the hood? I'd think that a method for turning an object into a string would have to loop through all the properties to accomplish the task.
Your assumption is that the implementation of ToString is shared between all instances of all anonymous types; that, for example, there is some helper that is logically something like you would do in JavaScript:
var s = "";
for (property in this)
s += property + ":" + this[property];
This assumption is wrong; there is no single one-size-fits-all implementation of ToString for anonymous types. Rather, the compiler knows what all the properties of the anonymous method are and so it generates a brand-new custom implementation of ToString for every distinct anonymous type.
In C#, the foreach loop does not do what the for-in loop does in JavaScript. The C# loop enumerates the members of a collection. The JS loop enumerates the properties of an object.
If you want to enumerate the properties of an object in C# you can do that, it just takes a bit more work:
var s = "";
foreach (PropertyInfo propertyInfo in this.GetType().GetProperties())
s += propertyInfo.Name + ":" + propertyInfo.GetValue(this).ToString();
You cannot do that because anonymous types don't implements IEnumerable interface - it's not a collection just one object. You have to explicitly print the values.
Console.WriteLine("{0}", o.RBI);
Console.WriteLine("{0}", o.Name);
But step yourself back. Do you need anonymous type? Define your own custom type.
class MyType // give it more meaningful name
{
public int RBI { get; set;}
public string Name { get; set;}
}
All anonymous objects have the same methods. They can be compared to each other as long as they have the same named fields with the same types, they all have a ToString() implementation that will give the string as you can see. But they don't have an implementation of an enumerator. Why should they? It's not like Javascript in that sense where you can enumerate over the property names/indices/whatever because... it's C#, that's just not how it is. Why would you think any different?
If you wanted something to work similarly, fortunately we have implicitly typed variables and reflection to help us out there.
var obj = new { Foo = "asd", Bar = "add", Gar = "123" };
var adapter = PropertyAdapter.Create(obj);
foreach (var name in adapter)
Console.WriteLine("obj.{0} = {1}", name, adapter[name]);
public static class PropertyAdapter
{
public static PropertyAdapter<T> Create<T>(T obj)
{
return new PropertyAdapter<T>(obj);
}
}
public class PropertyAdapter<T> : IEnumerable<string>
{
private T obj;
public PropertyAdapter(T obj) { this.obj = obj; }
public override string ToString()
{
return obj.ToString();
}
public object this[string name]
{
get
{
return typeof(T).GetProperty(name).GetValue(obj, null);
}
}
public IEnumerator<string> GetEnumerator()
{
return typeof(T)
.GetProperties()
.Select(pi => pi.Name)
.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
When you do
Console.WriteLine("{0}", o);
What's really happening is a call to Object.ToString(), which is inherited and has a built-in implementation for anonymous types that prints the properties and values.
On the other hand,
foreach (var i in o) { .. }
Can't work, because o must be an IEnumerable (or IEnumerable<>)
EDIT: The equivalent of what your expecting Enumerating over the string that is printed when using WriteLine can be achieved (for explanatory purposes, and otherwise useless) by doing:
foreach (var i in o.ToString()) { .. }
However as Jeff Mercado points out, that is not what you seem to want (it won't iterate over the properties - only over the individual characters of the already-formatted string)
How can I read the properties of an object that contains an element of array type using reflection in c#. If I have a method called GetMyProperties and I determine that the object is a custom type then how can I read the properties of an array and the values within. IsCustomType is method to determine if the type is custom type or not.
public void GetMyProperties(object obj)
{
foreach (PropertyInfo pinfo in obj.GetType().GetProperties())
{
if (!Helper.IsCustomType(pinfo.PropertyType))
{
string s = pinfo.GetValue(obj, null).ToString();
propArray.Add(s);
}
else
{
object o = pinfo.GetValue(obj, null);
GetMyProperties(o);
}
}
}
The scenario is, I have an object of ArrayClass and ArrayClass has two properties:
-string Id
-DeptArray[] depts
DeptArray is another class with 2 properties:
-string code
-string value
So, this methods gets an object of ArrayClass. I want to read all the properties to top-to-bottom and store name/value pair in a dictionary/list item. I am able to do it for value, custom, enum type. I got stuck with array of objects. Not sure how to do it.
Try this code:
public static void GetMyProperties(object obj)
{
foreach (PropertyInfo pinfo in obj.GetType().GetProperties())
{
var getMethod = pinfo.GetGetMethod();
if (getMethod.ReturnType.IsArray)
{
var arrayObject = getMethod.Invoke(obj, null);
foreach (object element in (Array) arrayObject)
{
foreach (PropertyInfo arrayObjPinfo in element.GetType().GetProperties())
{
Console.WriteLine(arrayObjPinfo.Name + ":" + arrayObjPinfo.GetGetMethod().Invoke(element, null).ToString());
}
}
}
}
}
I've tested this code and it resolves arrays through reflection correctly.
You'll need to retrieve the property value object and then call GetType() on it. Then you can do something like this:
var type = pinfo.GetGetMethod().Invoke(obj, new object[0]).GetType();
if (type.IsArray)
{
Array a = (Array)obj;
foreach (object arrayVal in a)
{
// reflect on arrayVal now
var elementType = arrayVal.GetType();
}
}
FYI -- I pulled this code from a recursive object formatting method (I would use JSON serialization for it now).