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

问题描述 投票:1回答:1

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

  • 它们包含一个名为“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。即使它是一个列表也不是。

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

c# reflection
1个回答
2
投票

我取消了原来的答案(下面)以真正解决这个问题而不知道属性的类型,而不是它是具有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};
}
}
© www.soinside.com 2019 - 2024. All rights reserved.