Scott Hanselman

Is this a good idea? Has it been done? Should it? An Aggregating ToString() implementation...

January 28, '05 Comments [14] Posted in ASP.NET | NUnit
Sponsored By

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. I am a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Friday, January 28, 2005 3:41:03 AM UTC
This is a fine idea. The only downside I see is that your format strings (or as I like to call them, templates) are coupled to that specific type, making it hard to use the same format template with several different values. But maybe that's exactly what you are looking for.
Sergio Pereira
Friday, January 28, 2005 3:47:28 AM UTC
This looks fine to me. It might be easier to use an external formatting object (i.e. StringFormatter.ToString( myObject, "My name is {FirstName} {LastName}"), just so that you wouldn't be limited to subclasses of StringFormattingObject or whatever. In other words, you could use it on objects that already exist.

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
Friday, January 28, 2005 3:57:57 AM UTC
Looks nice, reads well and definitely is a clarity improvement over ordinal style. It still makes me a little queasy since refactoring tools etc can't "see" the property references in the string. I would use this style for simple stuff, but not production code.
Friday, January 28, 2005 4:01:06 AM UTC
Looks ok to me, if it isn't too slow.

and Happy (belated) Birthday Scott !
Kent
Friday, January 28, 2005 4:07:41 AM UTC
I don't like it. The property names in the format string are _code_. Sounds like an interesting exercise, but as much as possible, I prefer to access my members out in the light of day where the compiler can see it.
Friday, January 28, 2005 5:21:58 AM UTC
> Am I smoking crack?

If you don't know the answer to that question, the answer is probably YES. ;)
Friday, January 28, 2005 6:14:07 AM UTC
It seems like you'd be limiting yourself to a particular output. In online banking apps, you frequently need the information displayed in a variety of ways. So if you override the ToString(), you'll get something nice for one DataGrid but that is completely wrong for another. I think it's better to leave the output decisions to the individual controls and pages instead of implementing it in a centralized manner.

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.
Friday, January 28, 2005 6:32:42 AM UTC
Wouldn't

string foo = p.StringFormat("My name is {FirstName} {LastName} and my birthday is {Birthdate:MM/dd/yyyy}");

be a slightly better name?
Ben
Friday, January 28, 2005 8:16:08 AM UTC
As you probably know Ruby can do this, but in a more general way. You can do this with any variable in the local scope (or perhaps any statement...I'm not actually a 'rubyist'). I think the syntax is

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?
Friday, January 28, 2005 11:20:57 AM UTC
I think your biggest problem here is the need for type safety in knowing that your propery names are always correctly referenced. Sure if you have a stable API and you know it's not changing that then may be acceptable, but if you have a volatile API where things are changing, even worse if they only change 1 time a year, then you will always have occasions where someone either took away or renamed a property and now you won't find it until Runtime (hopefully). So my gut feeling is that this is a bad idea because you are loosing all protection of having the compiler do it's thing and validate that you are actually referencing the correct property where you think you are.
Friday, January 28, 2005 4:42:25 PM UTC
I don't know how much I like it. One thing we do where I'm working now is maintain a debugging helper class. It's part of the living code base while we are developing, but it doesn't ship with the compiled code.

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.
Friday, January 28, 2005 7:06:32 PM UTC
It actually works, i've been using this for some time already. It is really usefull for making filters for DataView.RowFilter an for DeveloperExpress components such as gridControl and so on..
hu_ha
Saturday, January 29, 2005 5:16:01 PM UTC
Hey Scott,

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.
Saturday, January 29, 2005 8:37:54 PM UTC
The FxCop plugin idea is a great one. It's not exactly (at all) compile time, but it's certainly static analysis and it wouldn't be hard. Thanks Mark!
Scott Hanselman
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.