将整个对象转储到C#中的日志的最佳方法是什么?


129

因此,对于在运行时查看当前对象的状态,我真的很喜欢Visual Studio Instant窗口提供的功能。只是做一个简单的

? objectname

会给我一个很好格式化的对象“转储”。

有没有一种简单的方法可以在代码中执行此操作,因此在登录时可以执行类似的操作?


最后,我使用了T.Dump了很多。这是一个非常可靠的解决方案-您只需要注意递归。
Dan Esparza 2015年

这是一个古老的问题,但在许多搜索命中中都排在首位。对于将来的读者:请参见this vs extension。在VS2015中对我来说很棒。
杰西·古德

1
由于该VS插件未维护且缺少某些功能,因此将于2020年进行更新。下面的库在代码中做同样的事情-并且具有一些额外的功能,例如,它跟踪已经访问过的地方以避免循环:github.com/thomasgalliker/ObjectDumper
Nick Westgate

Answers:


55

您可以基于Linq示例附带的ObjectDumper代码。
还可以查看此相关问题的答案以获取样本。


5
它也不适用于数组(它仅显示数组的类型和长度,但不打印其内容)。
康拉德·莫劳斯基

5
现在可以使用ObjectDumper的nuget软件包。它还提供一个扩展方法DumpToStringDumpObject的类。便利。
IsmailS 2015年

2
w3wp.exe崩溃当我尝试使用ObjectDumper类似Request.DumpToString("aaa");
保罗

60

对于较大的对象图,我第二次使用Json,但策略略有不同。首先,我有一个易于调用的静态类,并带有包装Json转换的静态方法(注意:可以将其作为扩展方法)。

using Newtonsoft.Json;

public static class F
{
    public static string Dump(object obj)
    {
        return JsonConvert.SerializeObject(obj);
    }
}

然后在你的Immediate Window

var lookHere = F.Dump(myobj);

lookHere将自动显示在Locals带有$ 的窗口中,或者您可以向其中添加手表。在Value检查器中,列的右侧是一个带有下拉式插入标记的放大镜。选择下拉符号,然后选择Json visualizer。

Visual Studio 2013 Locals窗口的屏幕截图

我正在使用Visual Studio 2013。


2
SerializeObj-> SerializeObject?
怀斯曼2014年

太好了,谢谢。我无法在远程服务器上安装用于Visual Studio的调试工具,并且在asp.net mvc应用程序中,该工具运行得非常好。
Liam Kernighan '18

1
要获得良好的格式,您可以执行以下操作:Newtonsoft.Json.JsonConvert.SerializeObject(sampleData, Formatting.Indented)
Zorgarath

比尝试手动操作容易得多。它变得复杂
阿旬

26

我敢肯定有更好的方法可以做到这一点,但过去我曾使用过类似下面的方法将对象序列化为我可以记录的字符串:

  private string ObjectToXml(object output)
  {
     string objectAsXmlString;

     System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(output.GetType());
     using (System.IO.StringWriter sw = new System.IO.StringWriter())
     {
        try
        {
           xs.Serialize(sw, output);
           objectAsXmlString = sw.ToString();
        }
        catch (Exception ex)
        {
           objectAsXmlString = ex.ToString();
        }
     }

     return objectAsXmlString;
  }

您将看到该方法还可能返回异常,而不是序列化的对象,因此您将要确保要记录的对象是可序列化的。


2
考虑到回答后添加到C#的功能,该问题可能会很有用,以指出此实现可以很好地用作扩展方法。如果将其应用于Object类,并且在需要扩展的任何地方引用该扩展,则这可能是调用函数的便捷方法。
Nikita G.

我不断从中得到:Failed to access type 'System.__ComObject' failed。Noob to C#,不胜感激。
GuySoft

1
@GuySoft我怀疑您对象的属性之一或对象本身不可序列化。
Bernhard Hofmann 2014年

不幸的是,不能在没有无参数构造函数的类上使用此方法。它们不可序列化。
Jarekczek '16

22

您可以使用Visual Studio立即窗口

只需粘贴(actual显然更改为您的对象名称):

Newtonsoft.Json.JsonConvert.SerializeObject(actual);

它应该以JSON打印对象 在此处输入图片说明

您应该可以通过textmechanic文本工具记事本++复制它\""\r\n用空格替换换行的引号()和换行(),然后删除"开头和结尾的双引号()并将其粘贴到jsbeautifier中以使其更具可读性。

更新OP的评论

public static class Dumper
{
    public static void Dump(this object obj)
    {
        Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(obj)); // your logger
    }
}

这应该允许您转储任何对象。

希望这可以节省您一些时间。


谢谢。也许您没有在我最初的问题中发现它,但我表示我已经知道即时窗口,因此我想在登录应用程序时执行相同的操作。
Dan Esparza 2015年

@DanEsparza Console.Log(Newtonsoft.Json.JsonConvert.SerializeObject(actual));?:)是的,我确实很想念它。当您搜索google.co.uk
。– Matas Vaitkevicius 2015年

2
仅供参考,当您在C#字符串中包含JSON字符串时,请单击字符串右侧的望远镜图标,然后选择“文本可视化工具”。它将打开一个窗口,该窗口显示JSON字符串的纯文本版本(非转义引号或\ r \ n)。
Walter

16

ServiceStack.Text具有T.Dump()扩展方法,该完全可以做到这一点,以一种可读的格式递归转储任何类型的所有属性。

用法示例:

var model = new TestModel();
Console.WriteLine(model.Dump());

并输出:

{
    Int: 1,
    String: One,
    DateTime: 2010-04-11,
    Guid: c050437f6fcd46be9b2d0806a0860b3e,
    EmptyIntList: [],
    IntList:
    [
        1,
        2,
        3
    ],
    StringList:
    [
        one,
        two,
        three
    ],
    StringIntMap:
    {
        a: 1,
        b: 2,
        c: 3
    }
}

1
它不适用于字段。OP明确询问“整个对象”。
Konrad Morawski

5
He didn't say fields-他说entire objects,其中包括字段。他还提到了Visual Studio的即时窗口功能作为他想要实现的示例(“只要做一个简单的操作,? objectname就会为我提供格式良好的对象“转储””)。? objectname也会打印出所有字段。This has been immensely helpful - one of my most used extension methods to date-我不是在问它有用,只是怀疑它会转储整个对象。
Konrad Morawski 2012年

3
@KonradMorawski错误的整个对象意味着该对象的递归转储,而不是它包含字段,这很容易导致无限递归循环。您不应该假设其他人在暗示什么。我的回答既有意义又有帮助,您的否决+评论则不然。
mythz 2012年

1
@mythz是的,您当然需要防止堆栈溢出(例如,每个Int32字段都有一个MaxValue字段,它Int32本身就是一个...),这很不错,但这并不会改变对象(当然还有整个对象)的事实。 -也包括字段,而不仅仅是属性。而且,? objectnameImmediate Window 确实显示字段中(您未解决该问题)-不会触发无限循环。如果那是我的不赞成票,我可以将其撤回(如果您通过解锁将其放开,那就可以了)。我原则上还是不同意。
康拉德·莫劳斯基

4
-1表示仅链接的答案,尽管如果可以使用它看起来很棒!也许我是盲人,但我无法通过该链接找到来源;两个上传文件夹为空。代码是否太长而无法包含在答案中?

14

这是一种编写格式良好的平面对象的简单方法:

using Newtonsoft.Json.Linq;

Debug.WriteLine("The object is " + JObject.FromObject(theObjectToDump).ToString());

发生的情况是,该对象首先由转换为JSON内部表示JObject.FromObject,然后由转换为JSON字符串ToString。(当然,JSON字符串是一个简单对象的很好的表示形式,尤其是因为ToString它将包含换行符和缩进。)“ ToString”当然是多余的(因为使用+concat字符串和对象来隐含它),但是我有点想在这里指定。


5
JsonConvert.SerializeObject(apprec,Formatting.Indented)便于在日志中读取
Tertium

1
HotLicks-我想向您传达这一贡献对我来说有多重要。我需要对更新过程中发生的更改进行审核,而您刚刚将我的压力从“紧急”级别降低到了可管理的“担忧”级别。先生,谢谢您,愿您的生命
长寿

4

您可以使用反射并遍历所有对象属性,然后获取它们的值并将其保存到日志中。格式确实很简单(您可以使用\ t缩进对象属性及其值):

MyObject
    Property1 = value
    Property2 = value2
    OtherObject
       OtherProperty = value ...



3

以下是另一个具有相同功能(并处理嵌套属性)的版本,我认为它更简单(不依赖外部库,可以轻松修改以执行除日志记录之外的操作):

public class ObjectDumper
{
    public static string Dump(object obj)
    {
        return new ObjectDumper().DumpObject(obj);
    }

    StringBuilder _dumpBuilder = new StringBuilder();

    string DumpObject(object obj)
    {
        DumpObject(obj, 0);
        return _dumpBuilder.ToString();
    }

    void DumpObject(object obj, int nestingLevel = 0)
    {
        var nestingSpaces = "".PadLeft(nestingLevel * 4);

        if (obj == null)
        {
            _dumpBuilder.AppendFormat("{0}null\n", nestingSpaces);
        }
        else if (obj is string || obj.GetType().IsPrimitive)
        {
            _dumpBuilder.AppendFormat("{0}{1}\n", nestingSpaces, obj);
        }
        else if (ImplementsDictionary(obj.GetType()))
        {
            using (var e = ((dynamic)obj).GetEnumerator())
            {
                var enumerator = (IEnumerator)e;
                while (enumerator.MoveNext())
                {
                    dynamic p = enumerator.Current;

                    var key = p.Key;
                    var value = p.Value;
                    _dumpBuilder.AppendFormat("{0}{1} ({2})\n", nestingSpaces, key, value != null ? value.GetType().ToString() : "<null>");
                    DumpObject(value, nestingLevel + 1);
                }
            }
        }
        else if (obj is IEnumerable)
        {
            foreach (dynamic p in obj as IEnumerable)
            {
                DumpObject(p, nestingLevel);
            }
        }
        else
        {
            foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(obj))
            {
                string name = descriptor.Name;
                object value = descriptor.GetValue(obj);

                _dumpBuilder.AppendFormat("{0}{1} ({2})\n", nestingSpaces, name, value != null ? value.GetType().ToString() : "<null>");
                DumpObject(value, nestingLevel + 1);
            }
        }
    }

    bool ImplementsDictionary(Type t)
    {
        return t.GetInterfaces().Any(i => i.Name.Contains("IDictionary"));
    }
}

1
如果您Date在内部对象中有一个属性,这将可怕地死...只是说...
Noctis

2

您可以编写自己的WriteLine方法-

public static void WriteLine<T>(T obj)
    {
        var t = typeof(T);
        var props = t.GetProperties();
        StringBuilder sb = new StringBuilder();
        foreach (var item in props)
        {
            sb.Append($"{item.Name}:{item.GetValue(obj,null)}; ");
        }
        sb.AppendLine();
        Console.WriteLine(sb.ToString());
    }

像这样使用它

WriteLine(myObject);

要编写一个集合,我们可以使用-

 var ifaces = t.GetInterfaces();
        if (ifaces.Any(o => o.Name.StartsWith("ICollection")))
        {

            dynamic lst = t.GetMethod("GetEnumerator").Invoke(obj, null);
            while (lst.MoveNext())
            {
                WriteLine(lst.Current);
            }
        }   

该方法可能看起来像-

 public static void WriteLine<T>(T obj)
    {
        var t = typeof(T);
        var ifaces = t.GetInterfaces();
        if (ifaces.Any(o => o.Name.StartsWith("ICollection")))
        {

            dynamic lst = t.GetMethod("GetEnumerator").Invoke(obj, null);
            while (lst.MoveNext())
            {
                WriteLine(lst.Current);
            }
        }            
        else if (t.GetProperties().Any())
        {
            var props = t.GetProperties();
            StringBuilder sb = new StringBuilder();
            foreach (var item in props)
            {
                sb.Append($"{item.Name}:{item.GetValue(obj, null)}; ");
            }
            sb.AppendLine();
            Console.WriteLine(sb.ToString());
        }
    }

if, else if以这种方式使用和检查接口,属性,基本类型等以及递归(因为这是递归方法),我们可以实现对象转储,但这肯定是乏味的。使用Microsoft的LINQ Sample中的对象转储器可以节省您的时间。


出于好奇:它如何处理数组或列表?还是引用父对象的属性?
Dan Esparza

@DanEsparza感谢您向我展示更具体的方法。
Ariful Islam

2

基于@engineforce的答案,我制作了我在Xamarin解决方案的PCL项目中使用的此类:

/// <summary>
/// Based on: https://stackoverflow.com/a/42264037/6155481
/// </summary>
public class ObjectDumper
{
    public static string Dump(object obj)
    {
        return new ObjectDumper().DumpObject(obj);
    }

    StringBuilder _dumpBuilder = new StringBuilder();

    string DumpObject(object obj)
    {
        DumpObject(obj, 0);
        return _dumpBuilder.ToString();
    }

    void DumpObject(object obj, int nestingLevel)
    {
        var nestingSpaces = "".PadLeft(nestingLevel * 4);

        if (obj == null)
        {
            _dumpBuilder.AppendFormat("{0}null\n", nestingSpaces);
        }
        else if (obj is string || obj.GetType().GetTypeInfo().IsPrimitive || obj.GetType().GetTypeInfo().IsEnum)
        {
            _dumpBuilder.AppendFormat("{0}{1}\n", nestingSpaces, obj);
        }
        else if (ImplementsDictionary(obj.GetType()))
        {
            using (var e = ((dynamic)obj).GetEnumerator())
            {
                var enumerator = (IEnumerator)e;
                while (enumerator.MoveNext())
                {
                    dynamic p = enumerator.Current;

                    var key = p.Key;
                    var value = p.Value;
                    _dumpBuilder.AppendFormat("{0}{1} ({2})\n", nestingSpaces, key, value != null ? value.GetType().ToString() : "<null>");
                    DumpObject(value, nestingLevel + 1);
                }
            }
        }
        else if (obj is IEnumerable)
        {
            foreach (dynamic p in obj as IEnumerable)
            {
                DumpObject(p, nestingLevel);
            }
        }
        else
        {
            foreach (PropertyInfo descriptor in obj.GetType().GetRuntimeProperties())
            {
                string name = descriptor.Name;
                object value = descriptor.GetValue(obj);

                _dumpBuilder.AppendFormat("{0}{1} ({2})\n", nestingSpaces, name, value != null ? value.GetType().ToString() : "<null>");

                // TODO: Prevent recursion due to circular reference
                if (name == "Self" && HasBaseType(obj.GetType(), "NSObject"))
                {
                    // In ObjC I need to break the recursion when I find the Self property
                    // otherwise it will be an infinite recursion
                    Console.WriteLine($"Found Self! {obj.GetType()}");
                }
                else
                {
                    DumpObject(value, nestingLevel + 1);
                }
            }
        }
    }

    bool HasBaseType(Type type, string baseTypeName)
    {
        if (type == null) return false;

        string typeName = type.Name;

        if (baseTypeName == typeName) return true;

        return HasBaseType(type.GetTypeInfo().BaseType, baseTypeName);
    }

    bool ImplementsDictionary(Type t)
    {
        return t is IDictionary;
    }
}

0

以上所有路径均假定您的对象可序列化为XML或JSON,
或者您必须实现自己的解决方案。

但是最后,您仍然需要解决一些问题,例如

  • 对象递归
  • 不可序列化的对象
  • 例外情况
  • ...

加上日志,您需要更多信息:

  • 事件何时发生
  • 调用栈
  • 哪个三元组
  • 网络会话中的内容
  • 哪个IP地址
  • 网址
  • ...

有最好的解决方案,可以解决所有这些以及更多问题。
使用以下Nuget软件包:Desharp
适用于所有类型的应用程序-Web和桌面应用程序
请参阅Desharp Github文档。它具有许多配置选项

随便打电话:

Desharp.Debug.Log(anyException);
Desharp.Debug.Log(anyCustomValueObject);
Desharp.Debug.Log(anyNonserializableObject);
Desharp.Debug.Log(anyFunc);
Desharp.Debug.Log(anyFunc, Desharp.Level.EMERGENCY); // you can store into different files
  • 它可以将日志保存为漂亮的HTML(或TEXT格式,可配置)
  • 可以选择在后台线程中编写(可配置)
  • 它具有最大对象深度和最大字符串长度的选项(可配置)
  • 它对可迭代对象使用循环,对其他所有对象使用反向反射,
    实际上对.NET环境中可以找到的任何对象使用循环。

我相信这会有所帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.