Общий список общих объектов

Скажем, у меня есть объект, представляющий поле данных, этот объект нуждается в следующих свойствах: Name, Type, Value, Length. Вот объект:

class Field<T>
{
    public string Name { get; set; }
    public Type Type
    {
        get
        {
            return typeof(T);
        }
    }
    public int Length { get; set; }
    public T Value { get; set; }
}  

Я использовал generics, потому что я хочу заставить пользователя кода только иметь возможность назначить значение определенного типа.
Теперь проблема в том, когда я хочу создать список полей.
Если я создам список типа List<Field<object>>, тогда мы можем назначить любое значение для данного поля в списке, а когда мы запрашиваем Type, мы получим "object".
Дело в том, что в этом списке мне может понадобиться несколько полей, содержащих строки, немногие из которых содержат ints, даты и даже пользовательские объекты, которые, в свою очередь, будут иметь список полей...
Является ли Generics хорошим решением для чего-то подобного? Если да, то как мне его реализовать? Если нет, то что лучше?

--- EDIT ---
Просто добавьте еще немного фона:
1. Мне может понадобиться список полей, и каждое поле будет содержать разные типы данных, например:

List<Field<object>> lst = new List<Field<object>>();
lst.Add(new Field<string>());
lst.Add(new Field<int>());
lst.Add(new Field<SomeObjectFromMyApp>());

2. Позже мне придется запросить эти объекты и их атрибуты автоматически в цикле, что-то вроде этого:

foreach(Field<object> fld in lst)
{
    Type t = fld.Type;
    //do some other stuff
}

Ответ 1

Да, дженерики - хороший выбор. Ключом к достижению безопасности типа (и идентификация типа с атрибутом Type является добавление абстракции между списком и классом Field<T>.

Имейте Field<T> реализовать интерфейс IField. Этот интерфейс не нуждается ни в каких членах.

Затем объявите свой список как List<IField>.

Таким образом вы ограничиваете список только полями, но каждое поле может быть другого типа.

Чтобы затем прочитать значения позже, просто сделайте

foreach(var field in list)
{
    var type = field.Type;
    ....
}

Ответ 2

Я предлагаю вам определить интерфейс, и поле реализует этот интерфейс

public interface IField
    {

    }

public class Field<T> : IField
    {
        public string Name { get; set; }
        public Type Type
        {
            get
            {
                return typeof(T);
            }
        }
        public int Length { get; set; }
        public T Value { get; set; }
    }

чтобы вы могли написать этот код:

var list = new List<IField>();

теперь этот список может содержать любой объект типа Field<T>

Ответ 3

Как уже упоминалось несколько комментаторов, вы не можете получить доступ к свойству Type, если создаете пустой интерфейс, поэтому я бы предпочел сделать:

public interface IField
{
    Type Type { get; }

    string Name { get; set; }

    int Length { get; set; }
}

public class Field<T> : IField
{
    public string Name { get; set; }

    Type IField.Type => typeof(T);

    public int Length { get; set; }

    public T Value { get; set; }

    public override string ToString()
    {
        return Value.ToString();
    }
}

Затем вы можете проверить, какое значение имеет свойство value, и привести объект к нужному типу:

class Program
{
    static void Main(string[] args)
    {
        var fieldList = new List<IField>()
        {
            new Field<string>()
            {
                Value = "Hello World!", 
                Length = 12, 
                Name = "A string"
            },
            new Field<int>()
            {
                Value = 4711,
                Length = sizeof(int),
                Name = "An integer value"
            },
            new Field<double>()
            {
                Value = 2.4,
                Length = sizeof(double),
                Name = "A double value"
            },
        };

        foreach (var field in fieldList)
        {
            if (field.Type == typeof(string))
            {
                PrintField(field, "String value:");
            }
            else if (field.Type == typeof(int))
            {
                PrintField(field, "Integer value:");
            }
            else if (field.Type == typeof(double))
            {
                PrintField(field, "Double value:");
            }
        }
    }

    static void PrintField(IField field, string info)
    {
        Debug.WriteLine(info);
        Debug.WriteLine($"\tName: {field.Name}, Length: {field.Length}, Value: {field}");
    }

Код производит следующий вывод:

// String value:
//  Name: A string, Length: 12, Value: Hello World!
// Integer value:
//     Name: An integer value, Length: 4, Value: 4711
// Double value:
//     Name: A double value, Length: 8, Value: 2,4