使用反射动态重写ToString()方法

10
我通常会覆盖ToString()方法来输出属性名称以及与之相关联的值。我有点厌倦手写这些内容,所以我正在寻找一种动态解决方案。
主程序:
TestingClass tc = new TestingClass()
{
    Prop1 = "blah1",
    Prop2 = "blah2"
};
Console.WriteLine(tc.ToString());
Console.ReadLine();

测试类:

public class TestingClass
{
    public string Prop1 { get; set; }//properties
    public string Prop2 { get; set; }
    public void Method1(string a) { }//method
    public TestingClass() { }//const
    public override string ToString()
    {
        StringBuilder sb = new StringBuilder();
        foreach (Type type in System.Reflection.Assembly.GetExecutingAssembly().GetTypes())
        {
            foreach (System.Reflection.PropertyInfo property in type.GetProperties())
            {
                sb.Append(property.Name);
                sb.Append(": ");
                sb.Append(this.GetType().GetProperty(property.Name).Name);
                sb.Append(System.Environment.NewLine);
            }
        }
        return sb.ToString();
    }
}
这目前的输出结果是:
Prop1: System.String Prop1
Prop2: System.String Prop2

期望的输出:

Prop1: blah1
Prop2: blah2

我愿意接受其他解决方案,不一定要使用反射,只要能够产生所需的输出即可。


3
你需要使用 GetValue 方法。 - gdoron
通常情况下,您不应使用 ToString 返回所有属性值。相反,如果需要,您可以提供一个名为 GetPropertyInfo 的方法来实现此功能。 - Tim Schmelter
6个回答

20

这对我有效:

public class TestingClass
{
    public string Prop1 { get; set; }//properties
    public string Prop2 { get; set; }
    public void Method1(string a) { }//method
    public TestingClass() { }//const
    public override string ToString()
    {
        StringBuilder sb = new StringBuilder();
        foreach (System.Reflection.PropertyInfo property in this.GetType().GetProperties())
        {
            sb.Append(property.Name);
            sb.Append(": ");
            if (property.GetIndexParameters().Length > 0)
            {
                sb.Append("Indexed Property cannot be used");
            }
            else
            {
                sb.Append(property.GetValue(this, null));
            }

            sb.Append(System.Environment.NewLine);
        }

        return sb.ToString();
    }
}
为了让其在任何地方都可用,您可以创建一个扩展。
无法在扩展中覆盖方法,但仍应该简化您的生活。
public static class MyExtensions
{
    public static string ToStringExtension(this object obj)
    {
        StringBuilder sb = new StringBuilder();
        foreach (System.Reflection.PropertyInfo property in obj.GetType().GetProperties())
        {

            sb.Append(property.Name);
            sb.Append(": ");
            if (property.GetIndexParameters().Length > 0)
            {
                sb.Append("Indexed Property cannot be used");
            }
            else
            {
                sb.Append(property.GetValue(obj, null));
            }

            sb.Append(System.Environment.NewLine);
        }

        return sb.ToString();
    }
}
你可以在每个对象上调用ToStringExtension()
缺点是,对于列表等不完美地工作,例如:
var list = new List<string>();
// (filling list ommitted)
list.ToStringExtension();
// output:
// Capacity: 16
// Count: 11
// Item: Indexed Property cannot be used

使用反射的性能如何?Hanselman 的实现 http://www.hanselman.com/blog/ASmarterOrPureEvilToStringWithExtensionMethods.aspx 怎么样? - Kiquenet
工作时,对于可空属性 public int? Prop2 { get; set; }public DateTime? Prop3 { get; set; } ,该怎么处理呢? - Kiquenet

5

这里有一个扩展,它可以报告标准类型,例如字符串、整数和日期时间类型,但也可以报告字符串列表(如下面的AccessPoints所示,上面的答案无法处理)。请注意,输出是对齐的,例如:

Name         : Omegaman
ID           : 1
Role         : Admin
AccessPoints : Alpha, Beta, Gamma
WeekDays     : Mon, Tue
StartDate    : 3/18/2014 12:16:07 PM

以下是一个扩展,它可以接受任何类型的类。它会反射出公共和私有属性,并报告它们是否为空。
public static string ReportAllProperties<T>(this T instance) where T : class
{

    if (instance == null)
        return string.Empty;

    var strListType = typeof(List<string>);
    var strArrType  = typeof(string[]);

    var arrayTypes   = new[] { strListType, strArrType };
    var handledTypes = new[] { typeof(Int32), typeof(String), typeof(bool), typeof(DateTime), typeof(double), typeof(decimal), strListType, strArrType };

    var validProperties = instance.GetType()
                                  .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                                  .Where(prop => handledTypes.Contains(prop.PropertyType))
                                  .Where(prop => prop.GetValue(instance, null) != null)
                                  .ToList();

    var format = string.Format("{{0,-{0}}} : {{1}}", validProperties.Max(prp => prp.Name.Length));

    return string.Join(
             Environment.NewLine,
             validProperties.Select(prop => string.Format(format, 
                                                          prop.Name,
                                                          (arrayTypes.Contains(prop.PropertyType) ? string.Join(", ", (IEnumerable<string>)prop.GetValue(instance, null))
                                                                                                  : prop.GetValue(instance, null)))));
}

使用方法

myInstance.ReportAllProperties()

请注意,这是基于我的博客文章 C#: ToString To Report all Properties Even Private Ones Via Reflection,该文章提供了更强大的解释。


对于可空属性,使用以下代码 public int? Prop2 { get; set; }public DateTime? Prop3 { get; set; } 会起作用吗? - Kiquenet

1
我会使用JSON,Serializer会为您完成所有的艰苦工作:
    public static class ObjectExtensions
    {
        public static string ToStringEx(this object obj)
        {
            return JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true });
        }
    }

0

这是我找到的,适用于大多数复杂类型(包括列表):

public static string ToXml(object Obj, System.Type ObjType)
{
    try
    {
        XmlSerializer ser;
        XmlSerializerNamespaces SerializeObject = new mlSerializerNamespaces();
        ser = new XmlSerializer((ObjType));
        MemoryStream memStream;
        memStream = new MemoryStream();
        XmlTextWriter xmlWriter;
        xmlWriter = new XmlTextWriter(memStream, Encoding.UTF8);
        xmlWriter.Namespaces = true;
        XmlQualifiedName[] qualiArrayXML = SerializeObject.ToArray();
        ser.Serialize(xmlWriter, Obj);
        xmlWriter.Close();
        memStream.Close();
        string xml;
        xml = Encoding.UTF8.GetString(memStream.GetBuffer());
        xml = xml.Substring(xml.IndexOf(Convert.ToChar(60)));
        xml = xml.Substring(0, (xml.LastIndexOf(Convert.ToChar(62)) + 1));
        return xml;
    }
    catch (Exception ex)
    { return string.Empty; }
}

用法:

string classAasString = ClassToXml.ToXml(a, typeof(ClassA)); //whare ClassA is an object

0

所以我写了一个扩展方法,调用了一个已经解决了所有魔法的库。

"override string ToString()" vs (my) "ToStringDump"....

在展示代码之前,我喜欢使用扩展方法(在这种情况下是ToStringDump),因为我不必在我的POCO/DTO对象中添加ObjectDump引用。我认为POCO和DTO应该非常干净,甚至应该被隔离在自己的程序集中。这样,这些POCO/DTO对象就可以轻松地共享。

public static class ObjectDumpAdapter
{
    public static string ToStringDump(this object obj)
    {
        string returnValue = ObjectDumper.Dump(obj);
        return returnValue;
    }
}

我的dotnet core csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>netstandard2.0</TargetFrameworks>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="ObjectDumper.NET" Version="2.5.20033.1" />
  </ItemGroup>

</Project>

Nuget链接:

https://www.nuget.org/packages/ObjectDumper.NET/


引用:

ObjectDumper是一个实用程序,旨在将C#对象序列化为字符串,以便于调试和记录目的。

(来自https://nugetmusthaves.com/Package/ObjectDumper.NET)


GitHub链接:

https://github.com/thomasgalliker/ObjectDumper


0

我自己也遇到了这个问题,我正在寻找一种将对象序列化为可读字符串的选项。如果没有只读属性,XML序列化可以提供一个可读的字符串。但是,如果存在只读属性/字段,则无法使用XML序列化。

    public static string ToString(object serializeable)
    {
        var type = serializeable.GetType();
        try
        {
            var sw = new StringWriter();
            new XmlSerializer(type).Serialize(sw, serializeable);
            return sw.ToString();
        }
        catch
        {
            return type.FullName;
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接