如何动态遍历不同的对象列表并获取特定属性的值?

问题描述

我的问题很难解释:

我正在调用一个API,此API可以返回不同对象的列表,因此该对象的类型不能在LINQ查询中使用。无论返回的对象是什么类型,在每个对象或子对象中,都有一个称为“ FleetId”的属性

我想要实现的是遍历返回的列表并获取属性“ FleetId”的值,API调用结果之一如下所示:

enter image description here

如您所见,该对象具有属性UserRole,在这种情况下,“ FleetId”位于该列表中:

enter image description here

所以我要实现的目标:

无论“ FleetId”在哪里,我都需要迭代直到找到它。因此,迭代也需要遍历子对象(如果有)。

如何使用LINQ以最佳方式实现这一目标?

解决方法

我想这就是您使用Reflection所要查找的。​​ p>

public static void FindProperty(Type currentType)
{
    if (!types.Contains(currentType)) {
        types.Add(currentType);

        foreach (var info in currentType.GetProperties()) {

            if (info.Name.Equals("FleetId")) {
                // found
            }

            if (!info.PropertyType.IsPrimitive) {
                FindProperty(info.PropertyType);
            }
        }
    }
}

您可以使用PropertyInfo.GetValue获得FleetId的值。

为防止无限循环,请保留已知类型的列表

HashSet<Type> knownTypes = new HashSet<Type>();

然后可以简单地使用FindProperty(apiResukt.GetType())调用该方法。

基本思想是递归地遍历属性并检查其名称。如果它们不匹配,请继续。


我认为您的层次结构不会很深,否则使用Stack<T>而不是递归调用该方法可能是更好的选择。

,

您可以使用switch语句将对象转换为各自的类型。

var fleetId = 1;
var fleetWhereIdIs1 = Results.Where(obj => 
{
    switch(obj)
    {
        case User user:
            return user.UserRoles.Any(role => role.FleetId == fleetId);
        case OtherObj obj:
            // Get fleet id from other object
            return <other_condition_here>;
        default:
            // default condition if no other condition met
            return default;
    }
}).ToList();

或者使用C#8中的(IMO)更优雅的模式匹配开关

var fleetWhereIdIs1 = Results.Where(obj => 
{
    return obj switch
    {
        User user => user.UserRoles.Any(role => role.FleetId == fleetId),OtherObj obj =>  <other_condition_here>,_ => default
    };
}).ToList();
,

尝试以下操作:

    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            int FleetId = 123;
            List<User> users = new List<User>();

            List<User> fleetId = users.Where(x => x.UserRoles.FleetId == FleetId).ToList();
 
        }
    }
    public class User
    {
        public string Email { get; set; }
        public Boolean Enabled { get; set; }
        public string FirstName { get; set; }
        public int Id { get; set; }
        public DateTime LastLogin { get; set; }
        public string LastName { get; set; }
        public string Password { get; set; }
        public UserRoles UserRoles { get; set; }
    }
    public class UserRoles
    {
        public string Fleet { get; set; }
        public int FleetId { get; set; }
        public string Installation { get; set; }
        public int InstallationId { get; set; }
        public string Role { get; set; }
        public int RoleId { get; set; }
        public string User { get; set; }
        public int UserId { get; set; }
    }
,

类似这样的东西

private static IEnumerable<(object,object)> FindFleetId(object rootObj,int fleetId)
{
    //if the rootObj is null,return an empty list
    if (rootObj == null)
        Enumerable.Empty<(object,object)>();

    //call a local method to keep track of the rootObj and the currentObj
    return Parse(rootObj,rootObj,fleetId);
}

private static IEnumerable<(object,object)> Parse(object rootObj,object currentObj,int fleetId)
{
    //break the method if currentObj is null
    if (currentObj == null)
        yield break;

    var type = currentObj.GetType();
    //check if the type of the object is IEnumerable if it is enumerate the children,a string is an IEnumerable of char,so ignore a string
    var enumerable = currentObj as IEnumerable;
    if (enumerable != null && type != typeof(string))
    {
        //enumerate the list
        foreach (var item in enumerable)
        {
            //incase the rootObj is an IEnumerable,change the rootObj to the item inside the IEnumerable
           var newRootObj = object.ReferenceEquals(rootObj,currentObj) ? item : rootObj;
            foreach (var nestedObj in Parse(newRootObj,item,fleetId))
                yield return nestedObj;
        }

        yield break;
    }

    //get all properties
    var properties = type.GetProperties();
    foreach (var propertyInfo in properties)
    {
        //get the value of the property
        var propValue = propertyInfo.GetValue(currentObj);

        //check if the name is FleetId
        if (propertyInfo.Name == "FleetId")
        {
            //cast the value to int,and compare it with the pased parameter,return if true                    
            if (propValue is int fId && fId == fleetId)
                yield return (rootObj,currentObj);

            break;
        }
    
        //call method recursivly
        foreach (var nestedObj in Parse(rootObj,propValue,fleetId))
            yield return nestedObj;    
    }
}

此想法的返回类型为IEnumerable<(object,object)>,其中元组的Item1是链的根对象(级别0,零地线),而元组的Item2是实际具有成员FleetId的对象。

代码使用递归来解析对象的级别/深度。

要找到该成员并获取其使用反射的值,该成员必须是公共的。

包含测试用例的完整代码:

class Program
{
    static void Main(string[] args)
    {
        int fleetId = 1;

        var a = FindFleetId(TestA(),fleetId);
        foreach (var x in a)
            Console.WriteLine(x.Item1.ToString() + " | " + x.Item2.ToString());

        var b = FindFleetId(TestB(),fleetId);
        foreach (var x in b)
            Console.WriteLine(x.Item1.ToString() + " | " + x.Item2.ToString());

        var c = FindFleetId(TestC(),fleetId);
        foreach (var x in c)
            Console.WriteLine(x.Item1.ToString() + " | " + x.Item2.ToString());

        var d = FindFleetId(TestD(),fleetId);
        foreach (var x in d)
            Console.WriteLine(x.Item1.ToString() + " | " + x.Item2.ToString());

        Console.ReadKey();
    }

    private static IEnumerable<(object,int fleetId)
    {
        if (rootObj == null)
            Enumerable.Empty<(object,object)>();

        return Parse(rootObj,fleetId);
    }

    private static IEnumerable<(object,int fleetId)
    {
        if (currentObj == null)
            yield break;

        var type = currentObj.GetType();
        var enumerable = currentObj as IEnumerable;
        if (enumerable != null && type != typeof(string))
        {
            foreach (var item in enumerable)
            {
                var newRootObj = object.ReferenceEquals(rootObj,currentObj) ? item : rootObj;
                foreach (var nestedObj in Parse(newRootObj,fleetId))
                    yield return nestedObj;
            }

            yield break;
        }

        var properties = type.GetProperties();
        foreach (var propertyInfo in properties)
        {
            var propValue = propertyInfo.GetValue(currentObj);

            if (propertyInfo.Name == "FleetId")
            {
                if (propValue is int fId && fId == fleetId)
                    yield return (rootObj,currentObj);

                break;
            }
        
            foreach (var nestedObj in Parse(rootObj,fleetId))
                yield return nestedObj;    
        }
    }

    private static A1[] TestA()
    {
        return new[]
        {
            new A1() {A2 = new A2() {A3 = new A3() {FleetId = 1}}},new A1() {A2 = new A2() {A3 = new A3() {FleetId = 2}}},};
    }
    private static B1[] TestB()
    {
        return new[]
        {
            new B1() {FleetId = 1},new B1() {FleetId = 2},};
    }
    private static C1[] TestC()
    {
        return new[]
        {
            new C1() { C2 = new List<C2>() { new C2() { C3 = new C3(){FleetId = 1}}}},new C1() { C2 = new List<C2>() { new C2() { C3 = new C3(){FleetId = 2}}}},};
    }
    private static D1[] TestD()
    {
        return new[]
        {
            new D1() { D2 = new List<D2>() {new D2(){FleetId = 1}}},new D1() { D2 = new List<D2>() {new D2(){FleetId = 2}}},};
    }
}

public class A1
{
    public A2 A2 { get; set; }
    public override string ToString()
    {
        return "A1." + A2;
    }
}
public class A2
{
    public A3 A3 { get; set; }
    public override string ToString()
    {
        return "A2." + A3;
    }
}
public class A3
{
    public int FleetId { get; set; }
    public override string ToString()
    {
        return "A3." + FleetId;
    }
}

public class B1
{
    public int FleetId { get; set; }
    public override string ToString()
    {
        return "B1." + FleetId;
    }
}

public class C1
{
    public List<C2> C2 { get; set; }
    public override string ToString()
    {
        return "C1." + string.Join("|",C2);
    }
}
public class C2
{
    public C3 C3 { get; set; }
    public override string ToString()
    {
        return "C2." + C3;
    }
}
public class C3
{
    public int FleetId { get; set; }
    public override string ToString()
    {
        return "C3." + FleetId;
    }
}

public class D1
{
    public List<D2> D2 { get; set; }
    public override string ToString()
    {
        return "D1." + string.Join("|",D2);
    }
}
public class D2
{
    public int FleetId { get; set; }
    public override string ToString()
    {
        return "D2." + FleetId;
    }
}

查看实际效果: https://dotnetfiddle.net/mqWCVk

,

这是我通过对象深处某个位置的属性名称查找属性值的解决方案。我递归地遍历每个项目,并且当它不是简单类型时,我就会进入一个属性。如果最终找不到该属性,则返回null

我使用this中的here来检查简单类型。

这是我用LINQPad编写的完整代码。

void Main()
{
    var Things = new List<object>();
    Things.Add(new Car()
    {
        Make = "BMW",Color = "red",FleetId = "1A"
    });
    Things.Add(new Box()
    {
        Shape = "oblong",FleetId = "2B",Volume = 20
    });
    Things.Add(new Pretzel()
    {
        Baker = "Smith",Container = new Box()
        {
            Shape = "round",FleetId = "1A",Volume = 18
        },Weight = 300
    });
    Things.Add(new Whatever()
    {
        Something = "Dinglehopper",NotFleet = "XXX"
    });

    var wantedFleetId = "1A";
    var result = Things.Where(x => (string)GetNestedProperty(x,"FleetId") == wantedFleetId);

    Console.WriteLine($"Elements with FleetId {wantedFleetId}:");
    foreach (var element in result)
        Console.WriteLine(element.ToString());
}

object GetNestedProperty(object obj,string PropName)
{
    foreach (var prop in obj.GetType().GetProperties())
    {
        if (prop.Name == PropName)
            return prop.GetValue(obj);
        else
        {
            if (!IsSimpleType(prop.PropertyType))
            {
                var result = GetNestedProperty(prop.GetValue(obj),PropName);
                if (result != null)
                    return result;
            }
        }
    }
    return null;
}

// source: https://gist.github.com/jonathanconway/3330614
public bool IsSimpleType(Type type)
{
    return
        type.IsValueType ||
        type.IsPrimitive ||
        new Type[] {
                typeof(String),typeof(Decimal),typeof(DateTime),typeof(DateTimeOffset),typeof(TimeSpan),typeof(Guid)
        }.Contains(type) ||
        Convert.GetTypeCode(type) != TypeCode.Object;
}

class Car
{
    public string Make { get; set; }
    public string FleetId { get; set; }
    public string Color { get; set; }
    public override string ToString()
    {
        return $"Car: {Make} {FleetId} {Color}";
    }
}

class Box
{
    public string Shape { get; set; }
    public string FleetId { get; set; }
    public int Volume { get; set; }
    public override string ToString()
    {
        return $"Box: {Shape} {FleetId} {Volume}";
    }
}

class Pretzel
{
    public string Baker { get; set; }
    public Box Container { get; set; }
    public int Weight { get; set; }
    public override string ToString()
    {
        return $"Pretzel: {Baker} {Weight} {Container.ToString()}";
    }
}

class Whatever
{
    public string Something { get; set; }
    public string NotFleet { get; set; }
    public override string ToString()
    {
        return $"Whatever: {Something} {NotFleet}";
    }
}