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.
CodePudding user response:
This doesn't work. It doesn't appear to know about the Id property in this context.
Yes, cause you have not specified any constraints to the type parametr T
, so it can be anything. There are multiple options:
- Create an interface
IHaveId
and constraintT
to it (or useDataElement
):
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
CodePudding user response:
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