如何迭代一个C#对象,查找特定类型的所有实例,以便构建这些实例的单独列表?
我有一个类似的需求,除了它需要对源对象进行更深入的探索 下面是一个代码示例:如何迭代一个C#对象,查找特定类型的所有实例,以便构建这些实例的单独列表?,c#,reflection,properties,enumeration,C#,Reflection,Properties,Enumeration,我有一个类似的需求,除了它需要对源对象进行更深入的探索 下面是一个代码示例: public class Target {}; public class Analyzed { public Target EasyOne { get; set; } public IList<Target> ABitMoreTricky { get; set; } public IList<Tuple<string, Target>> Nightmare
public class Target {};
public class Analyzed
{
public Target EasyOne { get; set; }
public IList<Target> ABitMoreTricky { get; set; }
public IList<Tuple<string, Target>> Nightmare { get; set; }
}
公共类目标{};
公共类分析
{
公共目标EasyOne{get;set;}
公共IList ABITMORE{get;set;}
公共IList{get;set;}
}
从分析的实例
,我想提取所有目标
实例
为了便于勘探,我们可以假设:
目前,
EasyOne
是。。。简单,但我正在寻找一些策略,以使所有目标实例在更复杂的结构中丢失。您可以采用反射方式,对您知道的所有容器(IEnumerable、IDictionary、所有元组,谁知道还有什么)进行特殊处理,或者你可以真正实现@Adrian Iftode在评论中开玩笑说的话
我不认为您真的想要序列化为XML然后解析它。它可以工作,但它要求所有对象都是XML可序列化的,如果我没有弄错的话,这要求所有序列化的数据都是公共的
您应该使用普通的序列化,但定义自己的自定义格式化程序,该格式化程序只跟踪要查找的对象。下面是一个例子,大致如下:
public List<T> FindAllInstances<T>(object value) where T : class
{
HashSet<object> exploredObjects = new HashSet<object>();
List<T> found = new List<T>();
FindAllInstances(value, exploredObjects, found);
return found;
}
private void FindAllInstances<T>(object value, HashSet<object> exploredObjects, List<T> found) where T : class
{
if (value == null)
return;
if (exploredObjects.Contains(value))
return;
exploredObjects.Add(value);
IEnumerable enumerable = value as IEnumerable;
if (enumerable != null)
{
foreach(object item in enumerable)
{
FindAllInstances<T>(item, exploredObjects, found);
}
}
else
{
T possibleMatch = value as T;
if (possibleMatch != null)
{
found.Add(possibleMatch);
}
Type type = value.GetType();
PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetProperty);
foreach(PropertyInfo property in properties)
{
object propertyValue = property.GetValue(value, null);
FindAllInstances<T>(propertyValue, exploredObjects, found);
}
}
private void TestIt()
{
Analyzed analyzed = new Analyzed()
{
EasyOne = new Target(),
ABitMoreTricky = new List<Target>() { new Target() },
Nightmare = new List<Tuple<string, Target>>() { new Tuple<string, Target>("", new Target()) }
};
List<Target> found = FindAllInstances<Target>(analyzed);
MessageBox.Show(found.Count.ToString());
}
公共列表findallinstance(对象值),其中T:class
{
HashSet exploredObjects=新HashSet();
找到的列表=新列表();
FindAllInstances(值、探索对象、已发现);
发现退货;
}
私有void FindAllInstances(对象值、哈希集exploredObject、找到的列表),其中T:class
{
如果(值==null)
返回;
if(exploredObjects.Contains(value))
返回;
exploredObjects.Add(value);
IEnumerable enumerable=作为IEnumerable的值;
if(可枚举!=null)
{
foreach(可枚举中的对象项)
{
FindAllInstances(项目,探索对象,已发现);
}
}
其他的
{
T possibleMatch=值为T;
if(可能匹配!=null)
{
找到。添加(可能匹配);
}
Type Type=value.GetType();
PropertyInfo[]properties=type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetProperty);
foreach(属性中的PropertyInfo属性)
{
object propertyValue=property.GetValue(值,null);
FindAllInstances(propertyValue,ExploredObject,found);
}
}
私有无效测试()
{
已分析=新分析()
{
EasyOne=新目标(),
ABITMORE=new List(){new Target()},
噩梦=新列表(){new Tuple(“,new Target())}
};
找到的列表=FindAllInstances(已分析);
Show(found.Count.ToString());
}
您可以使用反射来完成此操作。有两个任务需要解决:
获取类型的所有属性。type.GetProperties()
将完成此操作
确定属性类型是属于Target
类型,还是属于泛型类型,并将Target
作为类型参数。可以使用type.IsGenericType
来测试类型是否为泛型和类型。GetGenericArguments
来获取实际的类型参数。如果找到匹配项,则应从开始递归此泛型类型1并执行2中描述的匹配
因此,通过对泛型类型使用反射和递归,您应该能够做您想要做的事情
Dim tTargets()={New Target(“Joe”)中的{New List(目标),New
Target(“Bob”)},新Target(“Veronica”),新元组(字符串,Target,
DateTime,Target)(“一个元组”,新目标(“Henry”),DateTime。现在,新目标
目标(“执事”)}
泛型参数本身没有实例。对于@M.Babcock的观点,我假设您指的是存储在IList噩梦中的每个元组中的目标实例?我不明白,您的示例有什么问题。使类可序列化,将其序列化为xml,提取目标节点,反序列化它们。这很简单一个笑话:P@AdrianIftode,我也很喜欢你的解决方案:DThanks a lot,RQDQ,这完美地回答了我的问题。我没有太多期望…你甚至管理了无限引用循环。如果你有索引属性(这个[int index]),行object propertyValue=property.GetValue(value,null);
将失败,很容易检查,但是你跳过了内容。我重写了它以使用FieldInfo,这和属性一样简单。嗨@Jaap,你介意解释一下“我重写它以使用FieldInfo”吗?干杯。我使用了字段而不是属性。在一些自动序列化场景中,字段更合适(当然也有很多缺点)。而不是op type.GetProperties,当时type.GetFields工作得很好。还有string
实现了IEnumerable。应该跳过stringsThanks来回答你的问题,Boo。它看起来像RQDQ(即使我不太习惯读VB)。是的,RQDQ比我快了。但既然我做了这项工作,我还是发布了它。
Sub ShowMeTheTargets(ByVal tRoot As Object, ByVal tLevel As Int32)
Dim tCount As Int64 = 0
Dim tCountName As String = "Length"
If Nothing Is tRoot Then
Exit Sub
End If
If tRoot.GetType Is GetType(Target) Then
RTB.AppendText("Found: " & CType(tRoot, Target).Name & vbCrLf)
'
' Assume Target is not a Target container.
'
Exit Sub
End If
If LEVEL_MAX = tLevel Then
'
' We don't want to scan this object graph any deeper
'
Exit Sub
End If
If (Nothing Is tRoot.GetType.GetInterface("IEnumerable")) Then
For Each tProperty As PropertyInfo In tRoot.GetType.GetProperties
ShowMeTheTargets(tProperty.GetValue(tRoot, Nothing), tLevel + 1)
Next
Else
For Each tObject As Object In tRoot
ShowMeTheTargets(tObject, tLevel + 1)
Next
RTB.AppendText(tCount & vbCrLf)
End If
End Sub