简体   繁体   中英

How to get item type and members from list of object?

I have a

List<object> list = new List<object>();
while (myReader.Read())
{
   string arrKablan = myReader["arrK_Title"].ToString();
   string arrTotal = myReader["arrTotal"].ToString();
   string _title = myReader["MF_Title"].ToString();
   string _path = myReader["MF_Path"].ToString();
   int _level = Convert.ToInt32(myReader["MF_Level"].ToString());

   list.Add(new { title = _title, path = _path, kablanim = arrKablan, total = arrTotal, level = _level });
}

I need to select just items where level == 1

i tried

list = list.where(item => item.level == 1);

but i get an error

'object' does not contain a definition for 'level' and no extension method 'level' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) 

i know that the compiler can get the type so he can know what it is "level". how can i achieve this kind of select, without to define a class ?

You have two ways of fixing this:

  1. Use a List<dynamic> instead of a List<object> . This will disable type checks. Drawback: This will disable type checks. :-)

  2. Let the compiler infer the correct type of your list. To do this, have your data layer return a DataTable instead of a DataReader and then use LINQ to create the list:

     var myList = (from drow in myDataTable.AsEnumerable() select new { kablanim = drow["arrK_Title"].ToString(), total = drow["arrTotal"].ToString(), ... }).ToList(); 

I can't see why you don't just make a concrete class:

public class Foo
{
    public string Title { get; set; }
    public string Path { get; set; }
    // etc, etc
}

Then

List<Foo> list = new List<Foo>();

while (myReader.Read())
{
   string arrKablan = myReader["arrK_Title"].ToString();
   string arrTotal = myReader["arrTotal"].ToString();
   string _title = myReader["MF_Title"].ToString();
   string _path = myReader["MF_Path"].ToString();
   int _level = Convert.ToInt32(myReader["MF_Level"].ToString());

   list.Add(new Foo { Title = _title, Path = _path, /* etc, etc */ });
}

then you call becomes

list = list.Where(item => item.Level == 1).ToList();

(Note the additional ToList call required to make the list assignment valid)

Just for completeness, you can also do this. Create a function to get a value from any object using reflection:

private T GetValue<T>(object obj, string property)
{
    return (T)obj.GetType()
                 .GetProperties()
                 .Single(p => p.Name == property)
                 .GetValue(obj);
}

And call it like this:

var filteredList = list.Where(item => GetValue<int>(item, "level") == 1);

You can get value of a property on anonymous class like this:

        var anon = new { Level = "level", Time = DateTime.Now };
        Type type = anon.GetType();

        var props = type.GetProperties();
        foreach (var propertyInfo in props)
        {
            if (propertyInfo.Name == "Level")
            {
                var x =propertyInfo.GetValue(anon);

            }
        }

I'm not sure if it is the best way to achieve that, but it is certainly possible.

You are adding object of anonymous class to the list. You can refer to this anonymous class field only inside the method you've defined it in and you should probably avoid adding it to the list, because there is now other way other then reflection or dynamic to access field of theese objects.

For example, you can access one of the elements like this: var list = new List();

    list.Add(new { field1 = "a", field2 = 2 });
    list.Add(new { field1 = "b", field2 = 3 });
    list.Add(new { field1 = "c", field2 = 4 });

    dynamic o = list[1];

    Console.WriteLine(o.field1);
    Console.WriteLine(o.field2);

But you should be aware, that dynamic feature has a big overhead on every member access.

If you really want to use lambdas, you can rewrite

list = list.where(item => item.level == 1);

like this

var filtered = list.Where(item =>
{
    dynamic ditem = item;
    return ditem.Level == 1;
});

but this is a bad approach.

The other approach is to use reflection and rewrite this lambda like this

var filtered = list.Where(item =>
{
    var field = item.GetType().GetField("level");
    return (int)field.GetValue(item) == 1; 
});

This is better than using dynamic because it has a smaller overhead, but can still be very costly.

Also it would probably be better to cache FieldInfo object outside of loop if your anonymous objects have same type. It can be done like this

var field = list.First().GetType().GetField("level");
var filtered = list.Where(item => (int)field.GetValue(item) == 1);

For performance reasons, Linq depends on metadata being available at compile time . By explicitly declaring List<object> you have typed the elements of this list as object which does not have a member level .

If you want to use Linq like this you have two options.

  1. Declare a class with a level member and use it to type the collection
  2. Declare an interface with a level member and use it to cast in the lambda expression

Option 1 is the preferred approach. Normally Linq is used with a database and the classes are generated by Visual Studio directly from the database. This is why nobody complains about the need for classes to supply metadata.

The following line creates anonymous class.

new { title = _title, path = _path, kablanim = arrKablan, total = arrTotal, level = _level });

You can't cast then your objects to anything meaningfull. Objects don't have those properties. You have to create a class by your own and use it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM