3

I have entity with nested list:

public class Article : MyEntityBase
{
    public Article()
    {
        Tags = new List<Tag>();
    }

    [MyAttribute]
    public string Title { get; set; }

    [MyAttribute]
    public virtual List<Tag> Tags { get; set; }
}

public class Tag : EntityBase
{
    public string Title { get; set; }
}

public abstract class MyEntityBase
{
    public Guid Id { get; set; }
}

Also I have function that collects all [MyAttribute] marked properties and do something operations with them:

public function OperateWithAttributes(IEnumerable<PropertyInfo> properties)
{
    foreach (var p in properties)
    {
        if (p.PropertyType == typeof(string))
        {
            // do something
        }
        else if (/* there are code that check property type is List<T> */)
        {
            /* there are code that iterate list */
        }
    }
}

Questions:

  • How to compare property type with List<T>?
  • How to iterate list if I know that it's inherited from EntityBase?

P.S

I'm using .NET 4.5

acelot
  • 752
  • 2
  • 7
  • 26

5 Answers5

4

How to compare property type with List<T>?

Correctly identifying something as a list is ... tricky; especially if you want to handle all edge-cases (a custom IList<Foo> implementation, or a class that subclasses List<T>, etc). A lot of framework code checks instead for "implements the non-generic IList, and has a non-object indexer":

    static Type GetListType(Type type)
    {
        if (type == null) return null;
        if (!typeof(IList).IsAssignableFrom(type)) return null;

        var indexer = type.GetProperty("Item", new[] { typeof(int) });
        if (indexer == null || indexer.PropertyType == typeof(object))
            return null;

        return indexer.PropertyType;
    }

How to iterate list if I know that it's inherited from EntityBase?

Assuming you mean that the items are inherited from EntityBase, and you have determined that it is a list (from the previous question), then the easiest option is IList and foreach:

var itemType = GetListType(p.PropertyType);
if(itemType != null && itemType.IsSubclassOf(typeof(EntityBase)))
{
    var list = (IList) p.GetValue(obj);
    foreach(EntityBase item in list)
    {
        // ...
    }
}

Note: if you are going to get the value anyway, you can also reverse this and use an is test before using GetListType:

var value = p.GetValue(obj);
Type itemType;
if(value is IList && (itemType = GetListType(p.PropertyType) != null)
      && itemType.IsSubclassOf(typeof(EntityBase)))
{
    var list = (IList)value;
    foreach(EntityBase item in list)
    {
        // ...
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
3

How to compare property type with List<T>:

if (p.PropertyType.IsGenericType && 
    p.PropertyType.GetGenericTypeDefinition() == typeof(List<>))

How to iterate list if I know that [its items are] inherited from EntityBase:

var listForIteration = (IEnumerable<EntityBase>)list;

foreach (var item in listForIteration)
{
}

A List<T> is not covariant, but (as of .NET 4.0) an IEnumerable<T> is.

UPDATE: After some comments, here's an answer to the question you didn't ask:

How to test if I can enumerate an object as a sequence of items that inherit from EntityBase, and if so, loop over it:

var listForIteration = list as IEnumerable<EntityBase>;

if (listForIteration != null)
{
    foreach (var item in listForIteration)
    {
    }
}

This will (on .NET 4.0 or later) allow you to iterate any collection of EntityBase (or derived) objects, even if the collection is not a List<> (except for those that don't implement IEnumerable<T>, but those are rare).

Kris Vandermotten
  • 10,111
  • 38
  • 49
  • 1
    "but an `IEnumerable` is" - Only in .NET 4. The OP is probably using .NET 4, but it's worth pointing out for others that read this. –  Oct 22 '13 at 09:26
  • "that it's inherited from EntityBase" should be "that its items inherit from EntityBase" probably :) – BartoszKP Oct 22 '13 at 09:26
  • Whether or not `IEnumerable` is covariant depends on the framework version; also, it *may or may not matter*, but note that this doesn't handle things like `public class MyCustomList : List` or `public class MyEventMoreCustomList : IList` – Marc Gravell Oct 22 '13 at 09:27
  • @BartoszKP only quoting the OP's question verbatim – Kris Vandermotten Oct 22 '13 at 09:28
  • @KrisVandermotten Yes I know, but obviously OP misspoken this one, so it should be corrected, for sake of strictness that we, programmers, should love and encourage ;) – BartoszKP Oct 22 '13 at 09:40
  • @EliArbel the edge-cases I mentioned relate to your first test involving `IsGenericType` and `GetGenericTypeDefinition() == typeof(List<>))` - where they most certainly *do not* work. The covariance issue I mentioned related to the framework version, where again: this is a problem on, say, 3.5 – Marc Gravell Oct 22 '13 at 10:27
  • @MarcGravell I know, but my understanding is that those edge cases are not what the OP is asking for (OP doesn't define "compare" but I assume "test equality" is intended), so I didn't update my answer. If the question would be "how can I test if I can iterate" the answer would simple by to cast to the covariant interface using the `as` operator. (Also, I think you're confusing me with Eli Arbel.) – Kris Vandermotten Oct 22 '13 at 11:08
1

You probably want something like

Type type = p.PropertyType;
if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>) &&
   typeof(EntityBase).IsAssignableFrom(type.GetGenericArguments()[0]))
{
    // iterate list
}
James
  • 80,725
  • 18
  • 167
  • 237
  • `type.GetGenericArguments()[0] is EntityBase` is wrong, that should be something like `typeof(EntityBase).IsAssignableFrom(type.GetGenericArguments()[0])` –  Oct 22 '13 at 09:21
1

If you only want to iterate the list, you can use IEnumerable<out T>'s covariance:

if (typeof(IEnumerable<EntityBase>).IsAssignableFrom(p.PropertyType))
{
    var enumerable = (IEnumerable<EntityBase>)p.GetValue(obj);
    foreach (var entity in entities)
    {
        // do something
    }
}
Eli Arbel
  • 22,391
  • 3
  • 45
  • 71
0

How to iterate list if I know that it's inherited from EntityBase?

You know it's a subclass of type EntityBase, so instead of using generics, why not just go for a good old superclass reference? Then there's no need to use generics in this scenario, when List<EntityBase> will do fine.

christopher
  • 26,815
  • 5
  • 55
  • 89