Rich.Carpenter
Rich.Carpenter

Reputation: 1066

Generic list of generic lists

I've been playing with various ways of using generics and have hit a road block.

Consider the following classes:

public abstract class DataElement
{
    public int Id { get; set; }
}

public class School : DataElement
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}
    
public class Course : DataElement
{
    public int Id { get; set; }
    public int SchoolId { get; set; }
    public string Name { get; set; } = string.Empty;
}
    
public class Student : DataElement
{
    public int Id { get; set; }
    public int CourseId { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
}

Considering a hypothetical scenario where none of this data changes, I'm trying to create a DataDictionary class to house those objects in their respective Lists all within one top-level List property. This crazy idea came to me, when I was writing code to load the different data types from JSON files. I was able to write one load method that could read all three types of data using generics, and that sent me down this particular rabbit hole.

public interface IDataDictionary
{
    public List<T> GetAllItemsFromList<T>();
    public T GetSingleItemFromList<T>(int id);
}

public class DataDictionary : IDataDictionary
{
    public List<IList> Data = new List<IList>();

    // Return all objects from the list of type T
    public List<T> GetAllItemsFromList<T>()
    {
        return Data.OfType<T>().ToList();  // This works, returning the appropriate list.
    }

    // Return specific object from the list of type T by Id property value
    public T GetSingleItemFromList<T>(int id)
    {
        List<T> list = Data.OfType<List<T>>().First();  // This works, resolving to the correct list (e.g. Courses).
        return list.Where(i => i.Id == id).First();  // This doesn't work. It doesn't appear to know about the Id property in this context.
    }
}

GetAllItemsFromList works fine, returning the appropriate list of items

List<School> newList = GetAllItemsFromList<School>();

However, I am unable to figure out how to return a single item by Id from its respective list.

School newSchool = GetSingleItemFromList<School>(1);

It could be that I'm just trying to be too clever with this, but I can't help but think there is a way to do this, and I'm just missing it.

Upvotes: 0

Views: 281

Answers (2)

Guru Stron
Guru Stron

Reputation: 143453

This doesn't work. It doesn't appear to know about the Id property in this context.

Yes, because you have not specified any constraints to the type parameter T, so it can be anything. There are multiple options:

  • Create an interface IHaveId and constraint T to it (or use DataElement):
public interface IHaveId
{
    int Id { get; set; }
}

public interface IDataDictionary
{
    public List<T> GetAllItemsFromList<T>();
    public T GetSingleItemFromList<T>(int id) where T : IHaveId; // or where T : DataElement
}

public class DataDictionary : IDataDictionary
{
    public T GetSingleItemFromList<T>(int id) where T : IHaveId // or where T : DataElement
    {
        List<T> list = Data.OfType<List<T>>().First(); 
        return list.Where(i => i.Id == id)
            .First(); 
    }
}
  • add additional parameter to select the id:

      public T GetSingleItemFromList<T>(int id, Func<T, int> idSelector)
      {
          List<T> list = Data.OfType<List<T>>().First(); 
          return list.First(i => idSelector(i) == id);
      }
    
  • use reflection - I would argue the least recommended option

Upvotes: 2

Mihinator3000
Mihinator3000

Reputation: 134

The problem is that T parameter in function GetSingleItemFromList<T>(int id) could be anything. In order of this to work you should add a constraint:

GetSingleItemFromList<T>(int id) where T: DataElement

Upvotes: 2

Related Questions