实现一个查找表,它接受T,并在类型为List的方法上返回属性

本文关键字:List 类型 属性 返回 方法 查找 一个 实现 | 更新日期: 2023-09-27 18:18:08

我有一个管理对象集合的类,例如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; }
}

实现一个查找表,它接受T,并在类型为List<T>的方法上返回属性

但是.ToList()创建了一个新的列表,而不是一个引用,所以_lookup[typeof(Car)]().Add(myCar)只被添加到字典列表

可以:

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);
    }
}

如果CarBike都来自同一个类或实现相同的接口,那就太好了。然后,您可以向Add方法添加类型约束,以获取编译错误,而不是ArgumentException

编辑

上面的简单解决方案有一个小问题。只有当添加到列表中的对象类型与_lookup字典中存储的类型完全一致时,它才会起作用。如果你试图添加一个来自CarBike的对象,它将抛出。

例如,如果你定义了一个类

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;
}

尝试以下方法:

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());

关于派生类

如果你有一些从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>方法返回对全功能List<T>的引用(而不仅仅是纯非泛型IList):

List<Car> cars = listManager.Get<Car>();
cars.Add(new Ferrari());
var coolCars = cars.OfType<Ferrari>();

所以你不需要在ListManager中重新实现List<T>方法来重新发明轮子。

您可以枚举所有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方法可以使用Dictionary缓存以提高其性能,因为它将始终返回相同类型的相同实例