Dynamic override of ToString() using Reflection

2019-04-06 10:11发布

问题:

I generally override the ToString() method to output the property names and the values associated to them. I got a bit tired of writing these by hand so I'm looking for a dynamic solution.

Main:

TestingClass tc = new TestingClass()
{
    Prop1 = "blah1",
    Prop2 = "blah2"
};
Console.WriteLine(tc.ToString());
Console.ReadLine();

TestingClass:

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

This currently outputs:

Prop1: System.String Prop1
Prop2: System.String Prop2

Desired Output:

Prop1: blah1
Prop2: blah2

I'm open for other solutions, it doesn't have to use reflection, it just has to produce the desired output.

回答1:

This works for me:

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

To make it available everywhere you can create an Extension.
It's not possible to override methods in an Extension, but still it should simplify your life.

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

You can then call ToStringExtension() on every object.
Downside is, it doesn't work perfectly for lists etc., example:

var list = new List<string>();
// (filling list ommitted)
list.ToStringExtension();
// output:
// Capacity: 16
// Count: 11
// Item: Indexed Property cannot be used


回答2:

Here is an extension which will report the standard types such as string, int and Datetime but will also report string lists (shown below in AccessPoints which the above answer could not handle). Note that the output is aligned such as:

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

Below is the extension which takes in any type as long as its a class. It then reflects off of the public and private properties and if they are not null reports them.

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

Usage

myInstance.ReportAllProperties()

Note that this is based off my blog article C#: ToString To Report all Properties Even Private Ones Via Reflection which provides a more robust explanation of what is going on.



回答3:

This is what I found, that works with most compicated-types (including List):

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

usage:

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


回答4:

I ran into this myself where I am looking for an option to serialize into something readable. If there are no read only properties xml serialization can give a readable string. However if there are read only properties / fields then xml serialization is not an option.

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