在类型为 List<T> 的方法上实现采用 T 和 returns 属性的查找 table
Implementing a lookup table that takes T and returns attributes on method of type List<T>
我有一个 class 来管理对象集合,例如List<Car>
和 List<Bike>
是属性。
我想找到一种方法来在查找中获取对每个集合的引用,这样我就可以实现 Add<Car>(myCar)
或 Add(myCar)
(带反射)等方法,它将将其添加到正确的集合中。
我尝试了以下方法,
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; }
}
但 .ToList()
创建了一个新列表而不是引用,因此 _lookup[typeof(Car)]().Add(myCar)
仅添加到字典列表中。
您可以枚举所有 ListManager 属性并按其类型进行过滤。这是一个工作示例:
public class Car
{
public int Wheels { get; set; }
}
public class Bike
{
public int Pedals { get; set; }
}
public class ListManager
{
//Defina all list properties here:
public List<Car> Car { get; } = new List<Car>();
public List<Bike> Bikes { get; } = new List<Bike>();
//Gets a list instance by its element type
public object GetList(Type ElemType)
{
//Get the first property that is of the generic type List<> and that it's first generic argument equals ElemType,
//then, obtain the value for that property
return GetType().GetProperties()
.Where(x =>
x.PropertyType.IsGenericType &&
x.PropertyType.GetGenericTypeDefinition() == typeof(List<>) &&
x.PropertyType.GetGenericArguments()[0] == ElemType).FirstOrDefault().GetValue(this);
}
public void Add(object Value)
{
var ElemType = Value.GetType();
var List = GetList(ElemType);
//If list had more Add method overloads you should get all of them and filter by some other criteria
var AddMethod = List.GetType().GetMethod("Add");
//Invoke the Add method for the List instance with the first parameter as Value
AddMethod.Invoke(List,new [] { Value });
}
}
和测试控制台程序:
class Program
{
static void Main(string[] args)
{
var L = new ListManager();
L.Add(new Car { Wheels = 4 });
L.Add(new Car { Wheels = 3 });
L.Add(new Bike { Pedals = 2 });
//Prints 2:
Console.WriteLine(L.Car.Count);
//Prints 1:
Console.WriteLine(L.Bikes.Count);
Console.ReadKey();
}
}
**注意:**可以使用字典缓存 GetList 方法以提高其性能,因为它将 return 始终是相同类型的相同实例
尝试以下方法:
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>(); }
}
}
用法:
var listManager = new ListManager();
listManager.Add(new Car());
关于派生classes
如果你有一些 class 来自 Car
,例如:
public class Ferrari : Car { }
并且出于某种原因您不想在字典中包含 List<Ferrari>
,但您希望将 Ferrari
添加到 List<Car>
。那么你应该明确指定泛型类型参数:
listManager.Add<Car>(new Ferrari());
重要的是编译器会在编译时检查 Ferrari
是 Car
,因此您不能将 Ferrari
添加到 List<Bike>
。
但在这种情况下,您可能会忘记在某处指定泛型类型参数,因此您会在 运行 时遇到异常。
为避免它,只需删除 Add<T>
方法。因此,您每次都必须明确指定集合的类型:
listManager.Get<Car>().Add(new Ferrari());
但是所有的类型检查都会在编译时进行。
此外,使用最后一种方法,您可以随心所欲地操作列表,因为 Get<T>
方法 returns 是对全功能 List<T>
的引用(不仅仅是纯粹的非泛型 IList
):
List<Car> cars = listManager.Get<Car>();
cars.Add(new Ferrari());
var coolCars = cars.OfType<Ferrari>();
因此您无需通过重新实现 ListManager
中的 List<T>
方法来重新发明轮子。
这会起作用:
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);
}
}
如果 Car
和 Bike
都派生自相同的 class 或实现相同的接口,那就太好了。然后你可以将类型约束添加到 Add
方法来获取编译错误而不是 ArgumentException
.
编辑
上面的简单解决方案存在一个小问题。只有当添加到列表中的对象类型与 _lookup
字典中存储的类型完全相同时,它才会起作用。如果您尝试添加从 Car
或 Bike
派生的对象,它将抛出。
例如,如果您定义 class
public class Batmobile : Car { }
然后
var listManager = new ListManager();
listManager.Add(new Batmobile());
会抛出 ArgumentException
.
要避免它,您将需要更复杂的类型查找方法。而不是简单的 _lookup[typeof(Car)]
它应该是:
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;
}
我有一个 class 来管理对象集合,例如List<Car>
和 List<Bike>
是属性。
我想找到一种方法来在查找中获取对每个集合的引用,这样我就可以实现 Add<Car>(myCar)
或 Add(myCar)
(带反射)等方法,它将将其添加到正确的集合中。
我尝试了以下方法,
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; }
}
但 .ToList()
创建了一个新列表而不是引用,因此 _lookup[typeof(Car)]().Add(myCar)
仅添加到字典列表中。
您可以枚举所有 ListManager 属性并按其类型进行过滤。这是一个工作示例:
public class Car
{
public int Wheels { get; set; }
}
public class Bike
{
public int Pedals { get; set; }
}
public class ListManager
{
//Defina all list properties here:
public List<Car> Car { get; } = new List<Car>();
public List<Bike> Bikes { get; } = new List<Bike>();
//Gets a list instance by its element type
public object GetList(Type ElemType)
{
//Get the first property that is of the generic type List<> and that it's first generic argument equals ElemType,
//then, obtain the value for that property
return GetType().GetProperties()
.Where(x =>
x.PropertyType.IsGenericType &&
x.PropertyType.GetGenericTypeDefinition() == typeof(List<>) &&
x.PropertyType.GetGenericArguments()[0] == ElemType).FirstOrDefault().GetValue(this);
}
public void Add(object Value)
{
var ElemType = Value.GetType();
var List = GetList(ElemType);
//If list had more Add method overloads you should get all of them and filter by some other criteria
var AddMethod = List.GetType().GetMethod("Add");
//Invoke the Add method for the List instance with the first parameter as Value
AddMethod.Invoke(List,new [] { Value });
}
}
和测试控制台程序:
class Program
{
static void Main(string[] args)
{
var L = new ListManager();
L.Add(new Car { Wheels = 4 });
L.Add(new Car { Wheels = 3 });
L.Add(new Bike { Pedals = 2 });
//Prints 2:
Console.WriteLine(L.Car.Count);
//Prints 1:
Console.WriteLine(L.Bikes.Count);
Console.ReadKey();
}
}
**注意:**可以使用字典缓存 GetList 方法以提高其性能,因为它将 return 始终是相同类型的相同实例
尝试以下方法:
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>(); }
}
}
用法:
var listManager = new ListManager();
listManager.Add(new Car());
关于派生classes
如果你有一些 class 来自 Car
,例如:
public class Ferrari : Car { }
并且出于某种原因您不想在字典中包含 List<Ferrari>
,但您希望将 Ferrari
添加到 List<Car>
。那么你应该明确指定泛型类型参数:
listManager.Add<Car>(new Ferrari());
重要的是编译器会在编译时检查 Ferrari
是 Car
,因此您不能将 Ferrari
添加到 List<Bike>
。
但在这种情况下,您可能会忘记在某处指定泛型类型参数,因此您会在 运行 时遇到异常。
为避免它,只需删除 Add<T>
方法。因此,您每次都必须明确指定集合的类型:
listManager.Get<Car>().Add(new Ferrari());
但是所有的类型检查都会在编译时进行。
此外,使用最后一种方法,您可以随心所欲地操作列表,因为 Get<T>
方法 returns 是对全功能 List<T>
的引用(不仅仅是纯粹的非泛型 IList
):
List<Car> cars = listManager.Get<Car>();
cars.Add(new Ferrari());
var coolCars = cars.OfType<Ferrari>();
因此您无需通过重新实现 ListManager
中的 List<T>
方法来重新发明轮子。
这会起作用:
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);
}
}
如果 Car
和 Bike
都派生自相同的 class 或实现相同的接口,那就太好了。然后你可以将类型约束添加到 Add
方法来获取编译错误而不是 ArgumentException
.
编辑
上面的简单解决方案存在一个小问题。只有当添加到列表中的对象类型与 _lookup
字典中存储的类型完全相同时,它才会起作用。如果您尝试添加从 Car
或 Bike
派生的对象,它将抛出。
例如,如果您定义 class
public class Batmobile : Car { }
然后
var listManager = new ListManager();
listManager.Add(new Batmobile());
会抛出 ArgumentException
.
要避免它,您将需要更复杂的类型查找方法。而不是简单的 _lookup[typeof(Car)]
它应该是:
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;
}