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

时间:2008-12-11 17:58:32

标签: c# visual-studio debugging object logging

因此,为了在运行时查看当前对象的状态,我非常喜欢Visual Studio立即窗口给我的内容。只是做一个简单的

? objectname

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

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

12 个答案:

答案 0 :(得分:53)

您可以使用Linq samples附带的ObjectDumper代码作为基础 还要看一下这个related question的答案来获取样本。

答案 1 :(得分:44)

对于更大的对象图,我第二次使用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。

Screenshot of Visual Studio 2013 Locals window

我正在使用Visual Studio 2013。

答案 2 :(得分:25)

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

  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;
  }

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

答案 3 :(得分:17)

我有一个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
    }
}

答案 4 :(得分:16)

您可以使用Visual Studio立即窗口

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

Newtonsoft.Json.JsonConvert.SerializeObject(actual);

它应该用JSON打印对象 enter image description here

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

更新OP的评论

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

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

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

答案 5 :(得分:12)

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

using Newtonsoft.Json.Linq;

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

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

答案 6 :(得分:4)

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

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

答案 7 :(得分:3)

我喜欢做的是重写ToString(),以便在类型名称之外获得更多有用的输出。这在调试器中很方便,您可以在不需要展开对象的情况下查看有关对象的信息。

答案 8 :(得分: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"));
    }
}

答案 9 :(得分: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;
    }
}

答案 10 :(得分: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中的对象转储程序可以节省您的时间。

答案 11 :(得分:0)

以上所有路径均假定您的对象可序列化为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环境中可以找到的任何东西

我相信这会有所帮助。

豫ICP备18024241号-1