在不知道元素类型的情况下将项添加到列表中

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在不知道元素类型的情况下将项添加到列表中相关的知识,希望对你有一定的参考价值。

我需要用特定数据填充列表。此列表是另一个对象的属性。该列表的元素具有以下规则:

  • 它们包含一个名为“Id”的int-Property,它是可写的
  • 他们有一个没有参数的构造函数

这是到目前为止的代码:(我没有添加任何可靠性检查或错误处理来保持此示例简单)

private void SetListProperty(string propertyName, object target, int[] ids) {
   PropertyInfo property=target.GetProperty(propertyName);
   Type propertyType=property.PropertyType();
   Type elementType=propertyType.GetElementType();
   PropertyInfo elementId=elementType.GetProperty("Id");

   var targetList=new List<>(elementType); // PseudoCode. Does not work

   foreach (int id in ids) {
     var element=Activator.CreateInstance(elementType);
     elementId.SetValue(element, id);
     targetList.Add(element);  // PseudoCode. Does not work
   }
   property.SetValue(target, targetList);
}

调用该方法的示例:

public class User {
  public int Id {get;set;}
  public string Name {get;set;}
}
public class House {
public int Id {get;set;}

public string HouseName {get; set;]}

public class Group {
  public string Name {get;set;}
  public List<User> Users {get;set;}
  public List<House> Houses {get;set;}
}

var group=new Group { Name="nerds"};  
SetListProperty("Users"), group, new int[] {1,2,3,4,5});
SetListProperty("Houses"), group, new int[] {1,2,3,4,5});

所以在调用该方法之后,group应该包含一个属性Users,它有5个元素,每个元素都有Id集。

我在这里看到了类似的问题,关于创建一个未知类型的List,但不是如何实际添加未知类型的单个项目到该列表。

(更新)我认为我的问题在一开始就不够清楚。在那个方法里面,我不知道属性Type。即使它是一个列表也不是。

我只将属性名称作为字符串。

答案

我取消了原来的答案(下面)以真正解决这个问题而不知道属性的类型,而不是它是具有id的项目......或者它是具有Id的可枚举对象。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace StackOverflowTests
{
    [TestClass]
    public class StackOverflow_49181925Tests
    {
        public interface IHasId
        {
            int Id { get; set; }
        }

        public class Foo : IHasId
        {
            public int Id { get; set; }
        }

        public class HasAFoo
        {
            public Foo Foo { get; set; }
        }

        public class HasManyFoos
        {
            public IEnumerable<Foo> Foos { get; set; }
        }

        public void SetPropertyIds(object target, string propertyName, IEnumerable<int> ids)
        {
            var property = target.GetType().GetProperty(propertyName);
            var propertyType = property.PropertyType;

            //is it enumerable?
            if (typeof(IEnumerable).IsAssignableFrom(propertyType))
            {
                var objectType = propertyType.GetGenericArguments().First();

                var list = Activator.CreateInstance(typeof(List<>).MakeGenericType(objectType)) as IList;

                foreach(var id in ids)
                {
                    var obj = Activator.CreateInstance(objectType) as IHasId;
                    ((IHasId)obj).Id = id;

                    list.Add(obj);
                }

                property.SetValue(target, list);
            }
            else
            {
                if(ids.Count() != 1) throw new ApplicationException("You're trying to set multiple Ids to a single property.");
                var objectType = propertyType;
                var obj = Activator.CreateInstance(objectType);
                ((IHasId)obj).Id = ids.First();
                property.SetValue(target, obj);
            }
        }  

        [TestMethod]
        public void TestHasAFoo()
        {
            var target = new HasAFoo();
            this.SetPropertyIds(target, "Foo", new[] { 1 });
            Assert.AreEqual(target.Foo.Id, 1);
        }

        [TestMethod]
        public void TestHasManyFoos()
        {
            var target = new HasManyFoos();
            this.SetPropertyIds(target, "Foos", new[] { 1, 2 });
            Assert.AreEqual(target.Foos.ElementAt(0).Id, 1);
            Assert.AreEqual(target.Foos.ElementAt(1).Id, 2);
        }
    }
}

下面的原始答案

有很多不同的方法来实现这一目标。实现接口的第三种方法可以为您提供很多帮助,因为大多数业务模型可能都有某种ID。 Linq版虽然简短又甜美。这些都只是主题的变化。

使用Linq:

group.Users = new[]{1,2,3}.Select(s=>new User{Id = s}).ToList();

使用泛型:

private IList<T> IntsToModels<T>(IEnumerable<int> ints, Action<T,int> setId) where T : new(){

return ints.Select(s=>{
var t = new T();
setId(t,s);

return t;
}).ToList();

}

使用泛型和接口

public interface IHasID{
int Id{get;set;}
}
//implement the IHasID interface
public class User : IHasID...

private IEnumerable<T> ToModels(IEnumerable<int> ints) where T : IHasID, new(){
foreach(var i in ints){
yield return new T{Id = i};
}
}

以上是关于在不知道元素类型的情况下将项添加到列表中的主要内容,如果未能解决你的问题,请参考以下文章

如何在不跟踪索引的情况下将元素附加到列表中?

你知道如何在不改变其他元素顺序的情况下将元素移动到第一个位置吗?

如何在不重新索引的情况下将项目添加到 laravel 列表集合中?

在不知道数据类型的情况下将值绑定到 PreparedStatement

javascript 可以在不指定 PHP 等键的情况下将元素添加到数组吗?

在不知道类型的情况下将程序集从文件加载到自定义 AppDomain