I have a class that manages collections of objects e.g. List<Car>
and List<Bike>
which are atribute.
I'd like to find a way to get a reference to each of those collections in a lookup so I can implement methods such as Add<Car>(myCar)
or Add(myCar)
(with reflection) and it will add it to the right collection.
I tried the following,
public class ListManager
{
private Dictionary<Type, Func<IEnumerable<object>>> _lookup
= new Dictionary<Type, Func<IEnumerable<object>>>();
public ListManager()
{
this._lookup.Add(typeof(Car), () => { return this.Cars.Cast<object>().ToList(); });
this._lookup.Add(typeof(Bike), () => { return this.Bikes.Cast<object>().ToList(); });
}
public List<Car> Cars { get; set; }
public List<Bike> Bikes { get; set; }
}
but .ToList()
creates a new list and not a reference, so _lookup[typeof(Car)]().Add(myCar)
is only added to the dictionary list.
This will work:
public class ListManager
{
private Dictionary<Type, IList> _lookup
= new Dictionary<Type, IList>();
public ListManager()
{
_lookup.Add(typeof(Car), new List<Car>());
_lookup.Add(typeof(Bike), new List<Bike>());
}
public List<Car> Cars
{
get { return (List<Car>)_lookup[typeof(Car)]; }
}
public List<Bike> Bikes
{
get { return (List<Bike>)_lookup[typeof(Bike)]; }
}
public void Add<T>(T obj)
{
if(!_lookup.ContainsKey(typeof(T))) throw new ArgumentException("obj");
var list = _lookup[typeof(T)];
list.Add(obj);
}
}
It would be nice if both Car
and Bike
are derived from the same class or implement the same interface. Then you can add type constraint to the Add
method to get compile errors instead of ArgumentException
.
Edit
There is a small problem with the simple solution above. It will only work if the type of the objects added to the list is exactly the type stored in the _lookup
dictionary. If you try to add an object derived from Car
or Bike
it will throw.
For example if you define a class
public class Batmobile : Car { }
Then
var listManager = new ListManager();
listManager.Add(new Batmobile());
will throw ArgumentException
.
To avoid it you will need a more complicated type lookup method. Instead of simple _lookup[typeof(Car)]
it should be:
private IList FindList(Type type)
{
// find all lists of type, any of its base types or implemented interfaces
var candidates = _lookup.Where(kvp => kvp.Key.IsAssignableFrom(type)).ToList();
if (candidates.Count == 1) return candidates[0].Value;
// return the list of the lowest type in the hierarchy
foreach (var candidate in candidates)
{
if (candidates.Count(kvp => candidate.Key.IsAssignableFrom(kvp.Key)) == 1)
return candidate.Value;
}
return null;
}
Try the following approach:
public class ListManager
{
private readonly Dictionary<Type, IList> _lookup = new Dictionary<Type, IList>();
public ListManager()
{
_lookup.Add(typeof(Car), new List<Car>());
_lookup.Add(typeof(Bike), new List<Bike>());
}
public List<T> Get<T>()
{
return _lookup[typeof(T)] as List<T>;
}
public void Add<T>(T item)
{
Get<T>().Add(item);
}
public List<Car> Cars
{
get { return Get<Car>(); }
}
public List<Bike> Bikes
{
get { return Get<Bike>(); }
}
}
Usage:
var listManager = new ListManager();
listManager.Add(new Car());
If you have some class derived from the Car
, for example:
public class Ferrari : Car { }
And for some reason you don't want to have a List<Ferrari>
in the dictionary, but you want to add Ferrari
to the List<Car>
. Then you should specify the generic type argument explicitly:
listManager.Add<Car>(new Ferrari());
It's important that the compiler checks that Ferrari
is a Car
at compile time, so you cannot add Ferrari
to List<Bike>
.
But in this case it is possible that you'll forget to specify a generic type argument somewhere and therefore you'll get an exception at run time.
To avoid it, just remove the Add<T>
method. Thus you'll must explicitly specify a type of the collection each time:
listManager.Get<Car>().Add(new Ferrari());
But all the type checks will be performed at compile time.
Moreover, using the last approach you are able to manipulate lists as you like, since the Get<T>
method returns a reference to the fully-functional List<T>
(not just pure non-generic IList
):
List<Car> cars = listManager.Get<Car>();
cars.Add(new Ferrari());
var coolCars = cars.OfType<Ferrari>();
So you don't need to reinvent the wheel by reimplementing List<T>
methods in the ListManager
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With