Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing an array of generic collections with each a different generic argument

During the development of one of my projects, I encountered an issue regarding generic types.

The project requires me to write a class that would act as a source of list objects. Suppose I had the following class:

public class TablesProvider
{
    private readonly List[] _tables;

    public TablesProvider()
    {
        // initialize the tables var here....
    }

    public List<TItem> GetTable<TItem>()
    {
        return (List<TItem>)_tables.Single(x => x is List<TItem>);
    }
}

This class obviously doesn't work, because the List type is a generic type and therefore the generic arguments should be specified.

So I made an abstract type called MyList, that would be derived by a more specific type MyList<TItem> in order to escape this requirement, and edited the TablesProvider a little.

public class TablesProvider
{
    private readonly MyList[] _tables;

    public TablesProvider()
    {
        // initialize the tables var here....
    }

    public MyList<TItem> GetTable<TItem>()
    {
        return (MyList<TItem>)_tables.Single(x => x is MyList<TItem>);
    }
}

public abstract class MyList
{
    // ...
}

public class MyList<TItem> : MyList, IList<TItem>
{
    private readonly List<TItem> _elements = new List<TItem>();

    public TItem this[int index]
    {
        get { return _elements[index]; }
        set { _elements[index] = value; }
    }

    // ... 
}

This works quite well. There is only one problem left. Suppose I had 45 different collections, each defined with a different generic argument. What would be the best way of initializing all of those collections? I cannot use a for loop here, since generic parameters are specified at compile-time and not at runtime, and therefore a construction like this wouldn't be possible:

 for (int i = 0; i < 45; i++)
      _tables[i] = new MyList<GenericParameters[i]>();

My ultimate goal is to have the luxury to just do something like this...

 var table = _tablesProvider.GetTable<SomeClass>();
 var element = table[3];
 var propertyValue = element.SomeProperty;

... without the need to cast the variable element in order to access its type-specific members.

It is probably worth mentioning that the amount of different list objects is fixed to 45. This will not change. In theory, I could initialize the array line by line, or have 45 properties or variables instead. Both of these options, however, sound as a rather cheap solution to me, but I will accept one of them if there is no other way.

Any of you got some ideas? Am I doing this completely wrong? Should I consider an other structure?

Thanks in advance.

like image 687
Jerre S Avatar asked Nov 01 '22 11:11

Jerre S


2 Answers

Yes, it is possible to do what you are describing if you use reflection.

Supposing that your hypothetical GenericParameters array is an array of Types (since you can't have an array of type identifiers), you can define this helper function:

private MyList MakeList(Type t)
{
    return (MyList)Activator.CreateInstance(typeof(MyList<>).MakeGenericType(t));
}

And that will allow you to do this:

public TablesProvider()
{
    var GenericParameters = new[] { typeof(string), typeof(int), typeof(DateTime) };

    _tables = new MyList[GenericParameters.Length];

    for (int i = 0; i < GenericParameters.Length; i++)
    {
        _tables[i] = MakeList(GenericParameters[i]);
    }
}

You can even use LINQ if you want:

public TablesProvider()
{
    var GenericParameters = new[] { typeof(string), typeof(int), typeof(DateTime) };

    _tables = GenericParameters.Select(MakeList).ToArray();
}


Previous answer:

Well, the reality is that you're going to have a list of 45 different types somewhere, which pretty much means you're going to have 45 different lines of similar code. So one could say the goal is to make those lines as concise as possible.

One way to do so would be to add a helper function:

private void AddTable<T>()
{
    _tables.Add(new MyTable<T>());
}

(this assumes changing _tables to a List<MyTable>)

Then you could just do:

AddTable<Type1>();
AddTable<Type2>();
AddTable<Type3>();
AddTable<Type4>();
like image 80
JLRishe Avatar answered Nov 15 '22 04:11

JLRishe


this implementation works

public class TablesProvider
{
    private readonly List<object> _tables;

    public TablesProvider()
    {
        _tables = new List<object>();
    }

    public IList<TItem> GetTable<TItem>()
    {
        var lst = (List<TItem>)_tables.SingleOrDefault(x => x is List<TItem>);
        if (lst == null)
        {
            lst = new List<TItem>();
            _tables.Add(lst);
        }
        return lst;
    }
}

it creates List of TItem when necessary; next time it returns the same list for TItem. it is lazy initialization

so you can do invoke

var table = _tablesProvider.GetTable<SomeClass>();

without any code like this:

for (int i = 0; i < 45; i++)
  _tables[i] = new MyList<GenericParameters[i]>();

it is not ThreadSafe

like image 45
ASh Avatar answered Nov 15 '22 05:11

ASh