Ole Albers
Ole Albers

Reputation: 9295

Adding items to a list without knowing the element type

I need to fill a list with specific data. This list is a property of another object. The elements of that List have the following rules:

This is the code so far: (I did not add any plausiblity checks or error handling to keep this example simple)

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

Example for calling that method:

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

So after calling that method, group should contain a property Users that has 5 elements each with the Id set.

I've seen similar questions here about creating a List of an unknown Type, but not how to actually add single items of an unknown type to that list.

(Update) I assume my problem was not clear enough in the beginning. Inside that method I do NOT know the property Type. Not even if it is a list.

I only have the property name as string.

Upvotes: 2

Views: 1243

Answers (1)

BlackjacketMack
BlackjacketMack

Reputation: 5692

I scrapped the original answer (below) to truly address doing this without knowing the type of property OTHER than that it is either an item that has an id...or it is an enumerable of objects that have an 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);
        }
    }
}

ORIGINAL ANSWER BELOW

Lots of different ways of accomplishing this. The third way which implements an interface can give you a lot of help since most of your business models probably have some sort of Id. The Linq version though is short and sweet. These are all just variations on a theme.

Using Linq:

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

Using Generics:

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();

}

Using Generics and Interfaces

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

Upvotes: 4

Related Questions