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

因此,对于在运行时查看当前对象的状态,我非常喜欢 VisualStudio 即时窗口提供的内容。只是做一个简单的

? objectname

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

有没有一种简单的方法可以在代码中实现这一点,这样我就可以在日志记录时做类似的事情?

174312 次浏览

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

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

您可以基于 Linq 样本附带的 ObjectDumper 代码。
也有一个看看这个 相关问题的答案,以获得一个样本。

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

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

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

我喜欢做的是重写 ToString () ,这样我就可以得到除了类型名之外更有用的输出。这在调试器中很方便,您可以查看所需的关于对象的信息,而无需展开它。

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

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

using Newtonsoft.Json.Linq;


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

首先通过 JObject.FromObject将对象转换为 JSON 内部表示形式,然后通过 ToString将其转换为 JSON 字符串。(当然,JSON 字符串是一个简单对象的非常好的表示形式,特别是因为 ToString将包含换行和缩进。)“ ToString”当然是无关的(因为它是通过使用 +连接字符串和对象来表示的) ,但是我有点喜欢在这里指定它。

对于更大的对象图,我支持使用 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窗口中,该窗口前面有一个 $,或者您可以为其添加一个手表。在检查员的 abc 1栏的右手边,有一个放大镜,旁边有一个下拉式插入符号。选择下拉符号并选择 Json 可视化工具。

Screenshot of Visual Studio 2013 Locals window

我正在使用 VisualStudio2013。

您可以使用 VisualStudio 即时窗口

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

Newtonsoft.Json.JsonConvert.SerializeObject(actual);

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

你应该能够复制它 超过文本机械文本工具或者 记事本 + + ,用 "替换转义引号(\") ,用空格替换换行(\r\n) ,然后从开头和结尾删除双引号(") ,粘贴到 美容师,使其更易读。

更新 OP 的评论

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

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

希望这能为你节省点时间。

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

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

下面是另一个版本,可以做同样的事情(并处理嵌套属性) ,我认为这个版本更简单(不依赖于外部库,并且可以很容易地修改以做日志记录以外的事情) :

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

基于@engineering force 的回答,我创建了这个类,我正在 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;
}
}

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

但是最终你还是会遇到这样的问题

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

加上日志,你想要更多的信息:

  • 事情发生的时候
  • Callstack
  • 哪条线
  • 什么是在网络会议
  • 哪个 IP 地址
  • 网址
  • ...

有一个最好的解决方案可以解决所有这些问题,甚至更多。
使用这个 Nuget 包: 非常清晰
适用于所有类型的应用程序-包括 网络和桌面应用程序
看,这是 < a href = “ https://Github.com/debug-Sharp/Desharp”rel = “ nofollow noReferrer”> 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 环境中找到的东西

我相信会有帮助的。

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

using YamlDotNet.Serialization;


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

会给你

- a
- b
- c

这里有一个更全面的例子 https://dotnetfiddle.net/KuV63n

现在,您甚至不需要外部依赖项,只需使用内置的 MicrosoftJsonSerializer即可。

using System;
using System.Text.Json;


namespace MyCompany.Core.Extensions
{
public static class ObjectExtensions
{
public static string Dump(this object obj)
{
try
{
return JsonSerializer.Serialize(obj);
}
catch(Exception)
{
return string.Empty;
}
}
}
}

请注意,您可以传递一个 JsonSerializerOptions参数来进一步定制您喜欢的序列化:

enter image description here

假设您想编写缩进的 JSON 以便于阅读... ... 我们将使用:

   new JsonSerializerOptions { WriteIndented = true }

#######

如果你想从 NewtonSoft.Json迁移到 System.Text.Json,这里有一个很好的指南:

将 Newtonsoft.Json 与 System.Text.Json 进行比较,并迁移到 System.Text.Json