简体   繁体   中英

Override ToString function of class

I have a class named Myclass with an override of ToString() like this:

class Field
{

}

class MyClass
{
   Field propretie1 
   Field propretie2
         .
         .
         .
  Field propretie15

  public override string ToString()
  {
     StringBuilder temp = new StringBuilder(); 
     temp.Append(propretie1.ToString())
     temp.Append("|"); 
     temp.Append(propretie2.ToString())
     temp.Append("|");
         . 
         .
     temp.Append(propretie15.ToString())

     return temp.ToString();         
  }
}

I'd like to know if there is a better way to get over all the properties of Myclass with the declaration order to implement the ToString function.

No, there is no way to do this by your requirements other than coding each function manually. It would be easy with reflection, but

The GetFields method does not return fields in a particular order, such as alphabetical or declaration order. Your code must not depend on the order in which fields are returned, because that order varies.

So there is no way to actually get the order of the declaration. You could try alphabetical order by ordering them yourself though.

var allProps = typeof(MyClass).GetProperties();  // or GetFields() if they are fields
Array.Sort(allProps, (x, y) => x.Name.CompareTo(y.Name));
return string.Join("|", allProps.Select(x => x.GetValue(this)));

This uses Linq Select and an overload of GetValue from .NET 4.5 (Visual Studio 2012).

.Net offers an XmlSerializer object aimed at giving you a representation of your object. You can take advantage of that, just grabbing text nodes and joining them:

public override string ToString()
{
    return this.Stringify(); //calls reusable code to "stringify" any object
}

//converts an object's properties to a string of pipe delimited values
public static string Stringify<T>(this T obj)
{
    var xs = new XmlSerializer(obj.GetType());
    var doc = new XDocument();
    using (var writer = doc.CreateWriter())
    {
        xs.Serialize(writer, obj);
    }
    var s = from text in doc.XPathSelectElements("//*[./text()]") select text.Value;
    return string.Join("|", s);
}

Calling ToString on properties which are complex classes is more complex... to do that use the XmlElement attribute on those properties so the serializer knows you want to output these properties, then allow implicit conversion to string so the serializer doesn't error. Weirdly you also need to implement implicit conversion from string too (I guess because the serializer has the ability to deserialize too); but that doesn't have to work. Very hacky.

An alternate is to make your child type serializable using the [Serializable] attribute, put [XmlIgnore] on any public properties, then create a property with a get method calling to ToString function, and a dummy set method (again to trick the serializer). Not great, but it works.

Working Example

using System;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Xml.XPath;
namespace StackOverflow
{
    public static class ObjectStringer
    {
        public static string Stringify<T>(this T obj)
        {
            var xs = new XmlSerializer(obj.GetType());
            var doc = new XDocument();
            using (var writer = doc.CreateWriter())
            {
                xs.Serialize(writer, obj);
            }
            var s = from text in doc.XPathSelectElements("//*[./text()]") select text.Value;
            return string.Join("|", s);
        }
    }
    public class Field
    {
        static int x = 0;
        int y;
        public Field()
        {
            y = ++x;
        }
        public override string ToString()
        {
            return y.ToString();
        }
        public static implicit operator String(Field f)
        {
            return f==null?null:f.ToString();
        }
        public static implicit operator Field(String s)
        {
            return s ?? "nasty hack to make serializer work";
        }
    }
    public class Demo
    {
        public string P1 { get; set; }
        public string X { get; set; } //something to show if we're pulling back results in order defined here, or if the system just goes alphabetically
        public string P2 { get; set; }
        public int P3 { get; set; }
        public DateTime P4 { get; set; }

        public Demo P5 { get; set; }
        [XmlElement(typeof(String))]
        public Field P6 { get; set; }
        [XmlElement(typeof(String))]
        public Field P7 { get; set; }

        public override string ToString()
        {
            return this.Stringify();
        }

    }

    public class Program
    {
        public static void Main(string[] args)
        {
            Demo d = new Demo() { P1 = "test1", X = "expert mode", P2 = "test2", P3 = 3, P4 = DateTime.UtcNow, P5 = new Demo() { P1 = "baby", P2 = "ooh" },P6=new Field(),P7=new Field() };
            //d.P5 = d; //this solution's not perfect - e.g. attempt to serialize a circular loop in the object's graph
            Console.WriteLine(d.ToString());
            Console.WriteLine("done");
            Console.ReadKey();
        }
    }

}

Alternative

[Serializable]
public class Field
{
    static int x = 0;
    int y;

    public string DummyToString { get { return this.ToString(); }  set { /*serializer hack*/ } }
    [XmlIgnore]
    public string DontShowMe { get; set; }


    public Field()
    {
        y = ++x;
        DontShowMe = "you shouldn't see this";
    }

    public override string ToString()
    {
        return string.Format("string me on #{0}", y);
    }
}

//Demo's Field properties no longer require XmlElement attributes; i.e.:
public Field P6 { get; set; }
public Field P7 { get; set; }

NB:

  • If you have child items which are complex types (eg P5 in the above example), how do you want them handled? Should their properties also be pipe delimited (in which case how do you know which properties relate to which objects)?
  • How should null values be handled - same as blanks (ie two pipes with nothing between them), or not output at all?
  • Would it be better to return the property names as well as values - so you're not reliant on the order / your output's more robust to future changes in class definition?
  • Perhaps the XMLSerializer on its own better suits your underlying requirement; assuming you want a robust way of representing your object's data?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM