Is this a good idea? Has it been done? Should it? An Aggregating ToString() implementation...
Travis and I were kicking around this idea. It's either already been done and it's a great idea, it's a good idea and no one has bothered, or it's stupid because _______.
We use a lot of "Domain Objects" like say, "public class Person." Why not have a "aggregated" ToString override like this:
Person p = new Person("Scott","Hanselman",new DateTime(1974,1,22);
//blah blah blah
string foo = p.ToString("My name is {FirstName} {LastName} and my birthday is {Birthdate:MM/dd/yyyy}");
Is this stupid? Would it gain you anything over:
string foo = String.Format("My name is {0} {1} and my birthday is {2:MM/dd/yyyy}",p.FirstName,p.LastName,p.BirthDate);
I've got it half done, but I wanted to know your thoughts before I finish it.
My thinking is that, even though it'd be slow (Reflection) it's useful for these reasons:
- Quick Testing - Seems convenient to me, useful for NUnit.
- Ultra-Late Bound - Ya, I know I'm Mr. So-Early-Bound-I-Generate-Everything but sometimes you just don't know until late, which leads me to:
- Externalization of Complex Formatted Strings - True, you're embedding knowledge of your properties in a string, but it'd allow you to add a different series of fields if another language required it. This would be an improvement over ordinal style ({0}, {1}) format strings, no? Perhaps a silly use case.
- DataBinding - You can let an ASP.NET DataGrid just call ToString on an object and it'll "do the right thing" as opposed to doing a TemplateColumn or an OnItemDataBound callback.
- It just calls down into the underlying object's ToString - It's basically using the aggregate ToString's format string to get Properties and Fields and call their respective ToString's using the embedded (after the colon) format string.
Am I smoking crack?
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Speaking of ToString(), here is an idea that I've used on my last several projects, and has come in handy in every one. I create a System.NamedObject class that takes a single parameter (name) in the constructor, has a single public field (Name), and overrides ToString() to return this.Name, of course.
This ends up being the base class for about 80% of my other classes. It just saves a little time, since I usually forget to implement ToString(), and end up with listboxes full of object type names. I think it would be an interesting addition to the BCL, but I'm not too worried about it. It's easy enough to create by hand.
- Joshua
If you don't know the answer to that question, the answer is probably YES. ;)
Another related issue is that it wouldn't be obvious at the page level what is to be output without having to go back to the domain object. Trivial, perhaps, but one more disruption in source code reading.
string foo = p.StringFormat("My name is {FirstName} {LastName} and my birthday is {Birthdate:MM/dd/yyyy}");
be a slightly better name?
s = "something"
t = "can I have #{s} like this in C#?"
I would love to have this feature in C#. Everyone unit tests these days anyway right?
It tends to be tightly coupled to our objects, in that it has very intimate knowledge of how our objects work. We could build something that uses reflection and dumps all the properties, but it's easier to just know that you want to see the "My name is" message and just build that using exposed properties. What I find really nice, is that when stepping though code, using the command window you can just call the debugging helper class and pass it whatever local objects you have around. So you can say OurDebugger.ShowMeYourVitals(myOject) and it will dump them all out to the command window. Another nice thing, is that it's easy to call them in an Nunit test and dump the output out to the debug window or something like that.
It helps for quick testing. But keeps it separate and out of your production code.
I think this is a great idea. I want to take this string formatting syntax and use it everywhere I create messages for human consumption.
Advantages:
1. When it comes time to translate your app into other languages, {0} and {1} provide few clues into part of speech. Take this phrase as an example:
"{0} flies on {1}."
As a translator, if you don't know what "{0}" and "{1}" are, it is impossible to translate this sentence into your target language. Is "flies" a verb or a noun? Is "0" an adjective (e.g., "House"), or a perhaps the name of a person? It's simply too ambiguous. And since translators almost never work in the source code, it's up to us as developers to provide a context that describes the values passed for "{0}" and "{1}". This context will usually be in the form of a comment near this string, and then a custom in-house tool will be built that extracts the string along with its explaining comment. It's a lot of work.
By contrast, using identifier format strings eliminates ambiguity:
"{CustomerName} flies on {DepartureDate}."
Now the burden on the developer to document context for the ordered parameters is gone.
2. Strings are easier to read during development and maintenance.
3. Order errors that may slip past the developer using the old format strings become obvious when looking at a string formatting with identifier format strings. For example, I've seen errors like this slip past the developer:
String.Format("Insert disk #{0} in drive {1}.", DriveLetter, DiskNumber)
The problem is that the variables are visually decoupled from the string. This is more likely to occur when the string is long and the call to Format is on a single line (and parameters are scrolled offscreen to the right). However, with the named parameters, the same mistake:
"Insert disk #{DriveLetter} in drive {DiskNumber}."
...is unlikely to ever happen.
Disadvantages:
1. There is no compile-time checking if you rename an identifier referenced in the string. This is a big deal IMO, because it could lead to a problem that doesn't surface until after you ship (e.g., rename a property and you don't get a compile error in the identifier format string). To lessen the impact of this, you could use discipline (and some test-generating code) to create test cases for any identifiers in a class that might be subject to change after the initial message is crafted. Also, it may be possible to create an FXCop plug-in to detect this.
2. There is no IntelliSense *inside* the string which would serve to ease the typing of identifier names. This could be mitigated with a DXCore plug-in that provides Intellassist suggestions inside the string. I'll help with this if you like.
Note: Scott, you mentioned performance concerns in your post caused by reflection. IMO, this will rarely be an issue, if ever. Formatting strings in this manner will nearly always be in response to a user action. I like to call the performance constraints that occur as the user is interacting with your app as "UI Time -- the time between the instantiating action (e.g., a key press) and the feedback the user sees. Users should see feedback in about a second or less for most actions. I bet you could format 1000-10000 strings using reflection in this time, and most of the time you would only be formatting a few strings in response to a user action, so I just don't see this ever becoming the source of a bottleneck.
Comments are closed.