1

假设我有两个类 Foo 和 Bar 如下

public class Foo
{
    private Bar _bar;
    private string _whatever = "whatever";
    public Foo()
    {
        _bar = new Bar();
    }

    public Bar TheBar
        {
            get
            {
                return _bar;
            }

        }
}

public class Bar
{
    public string Name { get; set; }
}

我有一个附加到使用这些类的进程的应用程序。我想在 .NET 堆中查看 Foo 类型的所有实例,并检查 .NET 堆中存在的所有 Foo 实例的 TheBar.Name 属性或 _whatever 字段。我可以找到类型,但我不确定如何获取实例并查看其属性。有什么想法吗?

using (DataTarget target = DataTarget.AttachToProcess(processId, 30000))
{
    string dacLocation = target.ClrVersions[0].TryGetDacLocation();
    ClrRuntime runtime = target.CreateRuntime(dacLocation);

    if (runtime != null)
    {
        ClrHeap heap = runtime.GetHeap();
        foreach (ulong obj in heap.EnumerateObjects())
        {
            ClrType type = heap.GetObjectType(obj);
            if (type.Name.Compare("Foo") == 0 )
            {
                // I would like to see value of TheBar.Name property or _whatever field of all instances of Foo type in the heap. How can I do it?
            }
        }
    }
}
4

3 回答 3

2

我认为您不能直接获取属性值,因为这需要您运行代码,而目标甚至可能不是进程,而是转储文件。

您绝对可以获得对象的字段及其值。ClrType 有一个 Fields 属性,您可以使用它来遍历字段。然后,您可以为 HasSimpleValue 为 true 的字段调用 GetFieldValue。

一个简单的例子:

private static void PrintFieldsForType(ClrRuntime runtime, string targetType)
{
    ClrHeap heap = runtime.GetHeap();
    foreach (var ptr in heap.EnumerateObjects())
    {
        ClrType type = heap.GetObjectType(ptr);
        if (type.Name == targetType)
        {
            foreach(var field in type.Fields)
            {
                if (field.HasSimpleValue)
                {
                    object value = field.GetFieldValue(ptr);
                    Console.WriteLine("{0} ({1}) = {2}", field.Name, field.Type.Name, value);
                }
                else
                {
                    Console.WriteLine("{0} ({1})", field.Name, field.Type.Name);
                }
            }
        }
    }
}

因此,您可以查找包含“Name”、“_name”或类似内容的字段。如果它是自动实现的属性,则名称将类似于<Name>k__BackingField.

你的场景有点复杂,因为你想进入另一个对象。为此,我们可以递归地检查字段。但是请注意,在一般情况下,您希望跟踪您访问过的对象,这样您就不会无限期地递归。

这是一个更适合于此的示例:

private static void PrintFieldsForType(ClrRuntime runtime, TextWriter writer, string targetType)
{
    ClrHeap heap = runtime.GetHeap();
    foreach (var ptr in heap.EnumerateObjects())
    {
        ClrType type = heap.GetObjectType(ptr);
        if (type.Name == targetType)
        {
            writer.WriteLine("{0}:", targetType);
            PrintFields(type, writer, ptr, 0);
        }
    }
}

private static void PrintFields(ClrType type, TextWriter writer, ulong ptr, int indentLevel)
{
    string indent = new string(' ', indentLevel * 4);
    foreach (var field in type.Fields)
    {
        writer.Write(indent);
        if (field.IsObjectReference() && field.Type.Name != "System.String")
        {
            writer.WriteLine("{0} ({1})", field.Name, field.Type.Name);
            ulong nextPtr = (ulong)field.GetFieldValue(ptr);
            PrintFields(field.Type, writer, nextPtr, indentLevel + 1);
        }
        else if (field.HasSimpleValue)
        {
            object value = field.GetFieldValue(ptr);
            writer.WriteLine("{0} ({1}) = {2}", field.Name, field.Type.Name, value);
        }
        else
        {
            writer.WriteLine("{0} ({1})", field.Name, field.Type.Name);
        }
    }
}
于 2015-05-05T21:25:52.350 回答
1

以下是在 LINQPad 中使用ClrMD.Extensions的方法:

var session = ClrMDSession.AttachToProcess(processId);
session.EnumerateClrObjects("*Foo").Dump(depth:3);
于 2015-05-06T18:41:05.630 回答
-2

我不知道是否可以以这种方式查询堆。但一个简单的解决方案是做这样的事情:

public class Foo
{
    public static List<WeakReference<Foo>> allInstances = new List<WeakReference<Foo>>();

    public Foo()
    {
        allInstances.Add(new WeakReference<Foo>(this));
    }
}

然后确保将其包装在 WeakReference 中,这样您的集合在进程结束之前不会将它们保留在堆中。

于 2015-05-05T20:59:23.100 回答