问题描述
我的问题很难解释:
我正在调用一个API,此API可以返回不同对象的列表,因此该对象的类型不能在LINQ查询中使用。无论返回的对象是什么类型,在每个对象或子对象中,都有一个称为“ FleetId”的属性。
我想要实现的是遍历返回的列表并获取属性“ FleetId”的值,API调用结果之一如下所示:
如您所见,该对象具有属性UserRole,在这种情况下,“ FleetId”位于该列表中:
所以我要实现的目标:
无论“ 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
。
这是我用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}";
}
}