如何迭代 List<T> 其中 T 是 MyClass 类型

3

我有一个带有嵌套列表的实体:

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; }
}

我还有一个函数,可以收集所有用[MyAttribute]标注的属性,并对它们执行某些操作:

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 */
        }
    }
}

问题:

  • 如何将属性类型与List<T>进行比较?
  • 如果我知道列表是从EntityBase继承而来,如何迭代它?

P.S

我正在使用.NET 4.5


https://dev59.com/eXRB5IYBdhLWcg3wpoxm - Abhitalks
5个回答

4
如何将属性类型与List<T>进行比较?
正确地将某个东西识别为列表是有点棘手的;特别是如果您想处理所有边缘情况(一个自定义的IList<Foo>实现,或者一个子类化List<T>的类等)。许多框架代码检查是否“实现了非泛型的IList,并且具有非object索引器”:
    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;
    }

如果我知道列表是从EntityBase继承而来,如何迭代列表?
假设您的意思是是从EntityBase继承而来,并且您已确定它是一个列表(从上一个问题中),那么最简单的选项是使用IListforeach
var itemType = GetListType(p.PropertyType);
if(itemType != null && itemType.IsSubclassOf(typeof(EntityBase)))
{
    var list = (IList) p.GetValue(obj);
    foreach(EntityBase item in list)
    {
        // ...
    }
}

注意:如果你无论如何都要获取值,你也可以将其反转并在使用GetListType之前进行一个is测试。
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)
    {
        // ...
    }
}

3
如何将属性类型与List<T>进行比较:
if (p.PropertyType.IsGenericType && 
    p.PropertyType.GetGenericTypeDefinition() == typeof(List<>))

如果我知道列表的项目是从EntityBase继承而来的,该如何迭代列表:
var listForIteration = (IEnumerable<EntityBase>)list;

foreach (var item in listForIteration)
{
}

一个 List<T> 不是协变的,但是(从 .NET 4.0 开始),一个 IEnumerable<T> 是。

更新:在一些评论之后,这里有一个你没有问到的问题的答案:

如何测试我是否可以将一个对象枚举为继承自 EntityBase 的项目序列,并循环遍历它:

var listForIteration = list as IEnumerable<EntityBase>;

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

在.NET 4.0或更高版本中,这将允许您迭代任何EntityBase(或其派生类)对象的集合,即使该集合不是List<>类型(除了那些不实现IEnumerable<T>接口的集合,但这些情况很少见)。


1
但是 IEnumerable<T> 是仅在 .NET 4 中才有的。OP 可能正在使用 .NET 4,但对于其他人来说也值得指出。 - user743382
无论 IEnumerable<T> 是否协变取决于框架版本;此外,可能重要也可能无关紧要,但请注意这不处理像 public class MyCustomList : List<Foo>public class MyEventMoreCustomList : IList<Bar> 这类的内容。 - Marc Gravell
@BartoszKP 只是引用原帖中的问题原文。 - Kris Vandermotten
1
@MarcGravell 在你提到的情况下,协变确实是有效的。它只需要类实现 IEnumerable<T> 即可,即使它在基类中也可以。 - Eli Arbel
@EliArbel 我提到的边缘情况与你第一个测试涉及的 IsGenericTypeGetGenericTypeDefinition() == typeof(List<>)) 有关 - 在那里它们肯定 不起作用。我提到的协变问题涉及框架版本,再次强调:这是在3.5上的一个问题。 - Marc Gravell
显示剩余3条评论

1
您可能需要类似以下内容的东西:
Type type = p.PropertyType;
if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>) &&
   typeof(EntityBase).IsAssignableFrom(type.GetGenericArguments()[0]))
{
    // iterate list
}

type.GetGenericArguments()[0] is EntityBase 是错误的,正确的应该是 typeof(EntityBase).IsAssignableFrom(type.GetGenericArguments()[0]) - user743382

1
如果您只想迭代列表,可以使用 IEnumerable<out T> 的协变性:
if (typeof(IEnumerable<EntityBase>).IsAssignableFrom(p.PropertyType))
{
    var enumerable = (IEnumerable<EntityBase>)p.GetValue(obj);
    foreach (var entity in entities)
    {
        // do something
    }
}

0
如果我知道它是从EntityBase继承而来的,如何迭代列表?
既然你知道它是类型实体基类的子类,那么为什么不使用一个好老的超类引用呢?在这种情况下不需要使用泛型,直接使用 List 就可以了。

@jon,那个转换不起作用,因为List<T>不是协变的。在.NET中,类不能是协变的,但接口可以。请看我的回答。 - Kris Vandermotten
无法将“System.Collections.Generic.List1[Tag]”强制转换为类型“System.Collections.Generic.List1[EntityBase]”。 - acelot
1
@KrisVandermotten:这就是我发表评论的原因。 - Jon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接