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

Posted

技术标签:

【中文标题】将整个对象转储到 C# 中的日志的最佳方法是啥?【英文标题】:What is the best way to dump entire objects to a log in C#?将整个对象转储到 C# 中的日志的最佳方法是什么? 【发布时间】:2010-09-26 11:50:21 【问题描述】:

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

? objectname

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

是否有一种简单的方法可以在代码中执行此操作,以便在记录时执行类似操作?

【问题讨论】:

最后,我使用了相当多的 T.Dump。这是一个非常可靠的解决方案——你只需要小心递归。 这是一个老问题,但在很多搜索结果中都排在首位。对于未来的读者:请参阅this vs extension。在 VS2015 中对我来说效果很好。 2020 年更新,因为该 VS 插件未维护且缺少某些功能。以下库在代码中做同样的事情 - 它有一些额外的功能,例如它跟踪已经访问过的位置以避免循环:github.com/thomasgalliker/ObjectDumper 【参考方案1】:

到目前为止,对我来说最简单和最整洁的方法是来自YamlDotNet 包的序列化程序。

using YamlDotNet.Serialization;

List<string> strings=new List<string>"a","b","c";
new Serializer().Serialize(strings)

会给你

- a
- b
- c

更全面的例子在这里https://dotnetfiddle.net/KuV63n

【讨论】:

我不得不承认:我喜欢这看起来多么干净【参考方案2】:

您可以使用 Visual Studio 即时窗口

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

Newtonsoft.Json.JsonConvert.SerializeObject(actual);

它应该以 JSON 格式打印对象

您应该能够复制它 over textmechanic text tool 或 notepad++ 并将转义引号 (\") 替换为 " 和换行符 (\r\n) 与空格,然后删除双引号 (")从头到尾粘贴到jsbeautifier,使其更具可读性。

更新 OP 的评论

public static class Dumper

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

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

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

【讨论】:

谢谢。也许您在我最初的问题中没有发现它,但我表示我已经知道即时窗口,并且我想在登录我的应用程序时做同样的事情。 @DanEsparza Console.Log(Newtonsoft.Json.JsonConvert.SerializeObject(actual)); ? :) 是的,我确实错过了。搜索google.co.uk/…时会出现这个问题 仅供参考,当 C# 字符串中有 JSON 字符串时,单击字符串右侧的望远镜图标并选择文本可视化工具。它将弹出一个窗口,显示 JSON 字符串的纯文本版本(不是转义引号或 \r\n)。【参考方案3】:

ServiceStack.Text 有一个 T.Dump() extension method 可以做到这一点,递归地以可读的格式递归转储任何类型的所有属性。

示例用法:

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
    

【讨论】:

它不适用于字段。 OP 明确询问“整个对象”。 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 - 我不是质疑它是否有用,只是它会转储整个对象。 @KonradMorawski 错误的整个对象表示对象的递归转储,而不是它包含字段,这很容易导致无限递归循环。你不应该假设别人在暗示什么。我的回答既相关又有帮助,你的反对票+评论不是。 @mythz 是的,当然你需要防止堆栈溢出(例如,每个Int32 字段都有一个MaxValue 字段,它本身就是一个Int32...),这很好点,但它并没有改变这样一个事实,即对象——当然还有整个对象——也由字段组成,而不仅仅是属性。更重要的是(您没有解决那个问题),? objectnameImmediate Window 确实 显示字段中 - 不会触发无限循环。如果那是关于我的反对票,我可以撤回它(如果你让我解锁它,那就是)。无论如何,我原则上不同意。 -1 本质上是一个仅链接的答案,尽管如果我可以使用它看起来很棒!也许我是盲人,但我无法通过该链接找到来源;两个上传文件夹是空的。代码是否太长而无法包含在答案中?【参考方案4】:

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

但最终你还是会遇到一些问题,比如

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

加日志你想了解更多信息:

事件发生时间 调用栈 哪三个广告 网络会话中的内容 哪个IP地址 网址 ...

有解决所有这些问题以及更多问题的最佳解决方案。使用此 Nuget 包:Desharp。 适用于所有类型的应用程序 - 网络和桌面应用程序。 看到它是 Desharp Github documentation。它有许多配置选项

随便打电话:

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 环境中找到的任何东西

我相信它会有所帮助。

【讨论】:

【参考方案5】:

根据@engineforce 的回答,我制作了我在 Xamarin 解决方案的 PCL 项目中使用的这个类:

/// <summary>
/// Based on: https://***.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("0null\n", nestingSpaces);
        
        else if (obj is string || obj.GetType().GetTypeInfo().IsPrimitive || obj.GetType().GetTypeInfo().IsEnum)
        
            _dumpBuilder.AppendFormat("01\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("01 (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("01 (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;
    

【讨论】:

【参考方案6】:

以下是做同样事情(并处理嵌套属性)的另一个版本,我认为它更简单(不依赖于外部库,并且可以轻松修改以执行日志记录以外的操作):

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("0null\n", nestingSpaces);
        
        else if (obj is string || obj.GetType().IsPrimitive)
        
            _dumpBuilder.AppendFormat("01\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("01 (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("01 (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"));
    

【讨论】:

如果你的内部对象中有一个Date 属性,这将非常糟糕......只是说......【参考方案7】:

您可以编写自己的 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 示例中的对象转储程序可以节省您的时间。

【讨论】:

出于好奇:这是如何处理数组或列表的?还是引用父对象的属性? @DanEsparza 谢谢你告诉我更具体的方法。【参考方案8】:

我发现了一个名为ObjectPrinter 的库,它可以轻松地将对象和集合转储到字符串(以及更多)。它完全符合我的需要。

【讨论】:

【参考方案9】:

对于更大的对象图,我支持使用 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 可视化工具。

我正在使用 Visual Studio 2013。

【讨论】:

SerializeObj -> SerializeObject? 太棒了,谢谢。我无法在我的远程服务器上安装 Visual Studio 的调试工具,而这个东西在我的 asp.net mvc 应用程序中运行得非常好。 为了更好的格式,你可以这样做:Newtonsoft.Json.JsonConvert.SerializeObject(sampleData, Formatting.Indented) 比尝试手动操作要容易得多。它变得复杂 在 .NET Core 3.1 和 .NET 5+ 中,您还可以使用开箱即用的 API System.Text.Json.JsonSerializer.Serialize(yourObject)【参考方案10】:

这是编写平面对象的一种非常简单的方法,格式很好:

using Newtonsoft.Json.Linq;

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

发生的事情是对象首先被JObject.FromObject转换为JSON内部表示,然后被ToString转换为JSON字符串。 (当然,JSON 字符串是一个简单对象的非常好的表示,特别是因为ToString 将包含换行符和缩进。)“ToString”当然是无关紧要的(因为它暗示使用 + 连接字符串和一个对象),但我有点想在这里指定它。

【讨论】:

JsonConvert.SerializeObject(apprec,Formatting.Indented) 方便阅读日志 HotLicks - 我想告诉你这个贡献现在对我有多重要。我要求对更新期间发生的变化进行审核,而您刚刚将我的压力从“恐慌”级别降低到可管理的“担忧”级别。谢谢先生,愿您一生幸福快乐【参考方案11】:

您可以基于 Linq samples 附带的 ObjectDumper 代码。 还可以查看related question 的答案以获取示例。

【讨论】:

它也不适用于数组(它只显示数组的类型和长度,但不打印其内容)。 nuget package for ObjectDumper 现在可用。它还提供了DumpToStringDumpObject 类的扩展方法。方便。 当我尝试像Request.DumpToString("aaa"); 一样使用ObjectDumper 时,w3wp.exe 崩溃【参考方案12】:

我喜欢做的是覆盖 ToString() 以便我获得超出类型名称的更有用的输出。这在调试器中很方便,您可以查看您想要的有关对象的信息,而无需扩展它。

【讨论】:

【参考方案13】:

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

  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;
  

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

【讨论】:

鉴于在您回答问题后添加到 C# 的功能,指出此实现作为扩展方法可以很好地工作可能会很有用。如果应用于 Object 类并且您在任何需要它的地方引用扩展,这可能是调用该函数的一种便捷方式。 我不断从中得到消息:Failed to access type 'System.__ComObject' failed。菜鸟到 c#,将不胜感激。 @GuySoft 我怀疑您的对象或对象本身的属性之一不可序列化。 不幸的是,您不能在没有无参数构造函数的类上使用此方法。它们不可序列化。【参考方案14】:

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

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

【讨论】:

以上是关于将整个对象转储到 C# 中的日志的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

将数组从 JavaScript 传递到 C# 的最佳方法?

使用 python 3.6 将多个文件并行加载到内存中的最佳方法是啥?

从 C# 中的矩形数组中提取一维数组的最佳方法是啥?

在 C# 字符串中的 HTML 中搜索特定文本并标记文本的最佳方法是啥?

在 C# 中用较小的数组复制/填充大数组的最佳方法是啥?

将索引对象添加到 Redux 存储(Redux Toolkit)的最佳方法是啥?