Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Obtaining a String Representation of an Object: ToString

You frequently need to obtain a string representation of an object. Usually, this is necessary when you want to display a numeric type (such as Byte, Int32, and Single) or a DateTime object to the user. Because the .NET Framework is an object-oriented platform, every type is responsible for providing code that converts an instance’s value to a string equivalent. When designing how types should ac- complish this, the designers of the FCL devised a pattern that would be used consistently throughout. In this section, I’ll describe this pattern.

You can obtain a string representation for any object by calling the ToString method. A public, virtual, parameterless ToString method is defined by System.Object and is therefore callable us- ing an instance of any type. Semantically, ToString returns a string representing the object’s current value, and this string should be formatted for the calling thread’s current culture; that is, the string representation of a number should use the proper decimal separator, digit-grouping symbol, and other elements associated with the culture assigned to the calling thread.

System.Object’s implementation of ToString simply returns the full name of the object’s type. This value isn’t particularly useful, but it is a reasonable default for the many types that can’t offer a sensible string. For example, what should a string representation of a FileStream or a Hashtable object look like?

All types that want to offer a reasonable way to obtain a string representing the current value of the object should override the ToString method. Many of the core types built into the FCL (Byte, Int32, UInt64, Double, and so on) override their ToString method and return a culturally aware string. In the Visual Studio debugger, a datatip is displayed when the mouse is placed over a particular variable. The text shown in the datatip is obtained by calling the object’s ToString method. So, when you define a class, you should always override the ToString method so that you get good debug- ging support.


Specific Formats and Cultures

The parameterless ToString method has two problems. First, the caller has no control over the formatting of the string. For example, an application might want to format a number into a currency string, decimal string, percent string, or hexadecimal string. Second, the caller can’t easily choose to format a string by using a specific culture. This second problem is more troublesome for server-side application code than for client-side code. On rare occasions, an application needs to format a string by using a culture other than the culture associated with the calling thread. To have more control over string formatting, you need a version of the ToString method that allows you to specify precise formatting and culture information.

Types that offer the caller a choice in formatting and culture implement the System.IFormat­ table interface.

 

public interface IFormattable {

String ToString(String format, IFormatProvider formatProvider);



}

 

In the FCL, all of the base types (Byte, SByte, Int16/UInt16, Int32/UInt32, Int64/UInt64, Single, Double, Decimal, and DateTime) implement this interface. In addition, some other types, such as Guid, implement it. Finally, every enumerated type definition will automatically implement the IFormattable interface so that a meaningful string symbol from an instance of the enumerated type can be obtained.

IFormattable’s ToString method takes two parameters. The first, format, is a string that tells the method how the object should be formatted. ToString’s second parameter, formatProvider, is an instance of a type that implements the System.IFormatProvider interface. This type supplies specific culture information to the ToString method. I’ll discuss how shortly.

The type implementing the IFormattable interface’s ToString method determines which for- mat strings it’s going to recognize. If you pass a format string that the type doesn’t recognize, the type is supposed to throw a System.FormatException.

Many of the types Microsoft has defined in the FCL recognize several formats. For example, the DateTime type supports “d” for short date, “D” for long date, “g” for general, “M” for month/day, “s” for sortable, “T” for long time, “u” for universal time in ISO 8601 format, “U” for universal time in full date format, “Y” for year/month, and others. All enumerated types support “G” for general, “F” for flags, “D” for decimal, and “X” for hexadecimal. I’ll cover formatting enumerated types in more detail in Chapter 15, “Enumerated Types and Bit Flags.”

Also, all of the built-in numeric types support “C” for currency, “D” for decimal, “E” for exponen- tial (scientific) notation, “F” for fixed-point, “G” for general, “N” for number, “P” for percent, “R” for round-trip, and “X” for hexadecimal. In fact, the numeric types also support picture format strings just in case the simple format strings don’t offer you exactly what you’re looking for. Picture format strings contain special characters that tell the type’s ToString method exactly how many digits to


show, exactly where to place a decimal separator, exactly how many digits to place after the decimal

separator, and so on. For complete information about format strings, see “Formatting Types” in the

.NET Framework SDK.

 

For most types, calling ToString and passing null for the format string is identical to calling ToString and passing “G” for the format string. In other words, objects format themselves using the “General format” by default. When implementing a type, choose a format that you think will be the most commonly used format; this format is the “General format.” By the way, the ToString method that takes no parameters assumes that the caller wants the “General format.”

So now that format strings are out of the way, let’s turn to culture information. By default, strings are formatted using the culture information associated with the calling thread. The parameterless ToString method certainly does this, and so does IFormattable’s ToString if you pass null for the formatProvider parameter.

Culture-sensitive information applies when you’re formatting numbers (including currency, in- tegers, floating point, percentages, dates, and times). The Guid type has a ToString method that returns only a string representing its value. There’s no need to consider a culture when generating the Guid’s string because GUIDs are used for programmatic purposes only.

When formatting a number, the ToString method sees what you’ve passed for the format­ Provider parameter. If null is passed, ToString determines the culture associated with the cal- ling thread by reading the System.Globalization.CultureInfo.CurrentCulture property. This property returns an instance of the System.Globalization.CultureInfo type.

Using this object, ToString reads its NumberFormat or DateTimeFormat property, depend- ing on whether a number or date/time is being formatted. These properties return an instance of System.Globalization.NumberFormatInfo or System.Globalization.DateTime­ FormatInfo, respectively. The NumberFormatInfo type defines a bunch of properties, such as

CurrencyDecimalSeparator, CurrencySymbol, NegativeSign, NumberGroupSeparator, and PercentSymbol. Likewise, the DateTimeFormatInfo type defines an assortment of properties, such as Calendar, DateSeparator, DayNames, LongDatePattern, ShortTimePattern, and TimeSeparator. ToString reads these properties when constructing and formatting a string.

When calling IFormattable’s ToString method, instead of passing null, you can pass a refer- ence to an object whose type implements the IFormatProvider interface.

 

public interface IFormatProvider { Object GetFormat(Type formatType);

}

 

Here’s the basic idea behind the IFormatProvider interface: when a type implements this inter- face, it is saying that an instance of the type is able to provide culture-specific formatting information and that the culture information associated with the calling thread should be ignored.


The System.Globalization.CultureInfo type is one of the very few types defined in the FCL that implements the IFormatProvider interface. If you want to format a string for, say, Vietnam, you’d construct a CultureInfo object and pass that object in as ToString’s formatProvider pa- rameter. The following code obtains a string representation of a Decimal numeric value formatted as currency appropriate for Vietnam.

 

Decimal price = 123.54M;

String s = price.ToString("C", new CultureInfo("vi­VN")); MessageBox.Show(s);

 

If you build and run this code, the message box shown in Figure 14-5 appears.

 
 

FIGURE 14-5Numeric value formatted correctly to represent Vietnamese currency.

 

Internally, Decimal’s ToString method sees that the formatProvider argument is not null and calls the object’s GetFormat method as follows.

 

NumberFormatInfo nfi = (NumberFormatInfo) formatProvider.GetFormat(typeof(NumberFormatInfo));

 

This is how ToString requests the appropriate number-formatting information from the (CultureInfo) object. Number types (such as Decimal) request only number-formatting infor- mation. But other types (such as DateTime) could call GetFormat like the following.

 

DateTimeFormatInfo dtfi = (DateTimeFormatInfo) formatProvider.GetFormat(typeof(DateTimeFormatInfo));

 

Actually, because GetFormat’s parameter can identify any type, the method is flexible enough to allow any type of format information to be requested. The types in the .NET Framework call Get­ Format, requesting only number or date/time information; in the future, other kinds of formatting information could be requested.

By the way, if you want to obtain a string for an object that isn’t formatted for any particular cul- ture, you should call System.Globalization.CultureInfo’s static InvariantCulture property and pass the object returned as ToString’s formatProvider parameter.

 

Decimal price = 123.54M;

String s = price.ToString("C", CultureInfo.InvariantCulture); MessageBox.Show(s);


If you build and run this code, the message box shown in Figure 14-6 appears. Notice the first character in the resulting string: ¤. This is the international sign for currency (U+00A4).

 
 

FIGURE 14-6Numeric value formatted to represent a culture-neutral currency.

 

Normally, you wouldn’t display a string formatted by using the invariant culture to a user. Typically,

you’d just save this string in a data file so that it could be parsed later.

 

In the FCL, just three types implement the IFormatProvider interface. The first is CultureInfo, which I’ve already explained. The other two are NumberFormatInfo and DateTimeFormatInfo.

When GetFormat is called on a NumberFormatInfo object, the method checks whether the type being requested is a NumberFormatInfo. If it is, this is returned; if it’s not, null is returned. Simi- larly, calling GetFormat on a DateTimeFormatInfo object returns this if a DateTimeFormatInfo is requested and null if it’s not. These two types implement this interface simply as a programming convenience. When trying to obtain a string representation of an object, the caller commonly speci- fies a format and uses the culture associated with the calling thread. For this reason, you often call ToString, passing a string for the format parameter and null for the formatProvider param- eter. To make calling ToString easier for you, many types offer several overloads of the ToString method. For example, the Decimal type offers four different ToString methods.

 

// This version calls ToString(null, null).

// Meaning: General numeric format, thread's culture information public override String ToString();

 

// This version is where the actual implementation of ToString goes.

// This version implements IFormattable's ToString method.

// Meaning: Caller­specified format and culture information

public String ToString(String format, IFormatProvider formatProvider);

 

// This version simply calls ToString(format, null).

// Meaning: Caller­specified format, thread's culture information public String ToString(String format);

 

// This version simply calls ToString(null, formatProvider).

// This version implements IConvertible's ToString method.

// Meaning: General format, caller­specified culture information public String ToString(IFormatProvider formatProvider);


Formatting Multiple Objects into a Single String

So far, I’ve explained how an individual type formats its own objects. At times, however, you want to construct strings consisting of many formatted objects. For example, the following string has a date, a person’s name, and an age.

 

String s = String.Format("On {0}, {1} is {2} years old.", new DateTime(2012, 4, 22, 14, 35, 5), "Aidan", 9);

Console.WriteLine(s);

 

If you build and run this code where “en-US” is the thread’s current culture, you’ll see the following

line of output.

 

On 4/22/2012 2:35:05 PM, Aidan is 9 years old.

 

String’s static Format method takes a format string that identifies replaceable parameters by us- ing numbers in braces. The format string used in this example tells the Format method to replace {0} with the first parameter after the format string (the date/time), replace {1} with the second parameter after the format string (“Aidan”), and replace {2} with the third parameter after the format string (7).

Internally, the Format method calls each object’s ToString method to obtain a string represen- tation for the object. Then the returned strings are all appended and the complete, final string is returned. This is all fine and good, but it means that all of the objects are formatted by using their general format and the calling thread’s culture information.

You can have more control when formatting an object if you specify format information within braces. For example, the following code is identical to the previous example except that I’ve added formatting information to replaceable parameters 0 and 2.

 

String s = String.Format("On {0:D}, {1} is {2:E} years old.", new DateTime(2012, 4, 22, 14, 35, 5), "Aidan", 9);

Console.WriteLine(s);

 

If you build and run this code where “en-US” is the thread’s current culture, you’ll see the following

line of output.

 

On Sunday, April 22, 2012, Aidan is 9.000000E+000 years old.

 

When the Format method parses the format string, it sees that replaceable parameter 0 should have its IFormattable interface’s ToString method called passing "D" and null for its two param- eters. Likewise, Format calls replaceable parameter 2’s IFormattable ToString method, passing "E" and null. If the type doesn’t implement the IFormattable interface, Format calls its param- eterless ToString method inherited from Object (and possibly overridden), and the default format is appended into the resulting string.

The String class offers several overloads of the static Format method. One version takes an object that implements the IFormatProvider interface so that you can format all of the replace- able parameters by using caller-specified culture information. Obviously, Format calls each object’s IFormattable.ToString method, passing it whatever IFormatProvider object was passed to Format.


If you’re using StringBuilder instead of String to construct a string, you can call String­ Builder’s AppendFormat method. This method works exactly as String’s Format method except that it formats a string and appends to the StringBuilder’s character array. As does String’s Format, AppendFormat takes a format string, and there’s a version that takes an IFormat­ Provider.

System.Console offers Write and WriteLine methods that also take format strings and re- placeable parameters. However, there are no overloads of Console’s Write and WriteLine methods that allow you to pass an IFormatProvider. If you want to format a string for a specific culture, you have to call String’s Format method, first passing the desired IFormatProvider object and then passing the resulting string to Console’s Write or WriteLine method. This shouldn’t be a big deal because, as I said earlier, it’s rare for client-side code to format a string by using a culture other than the one associated with the calling thread.

 

Providing Your Own Custom Formatter

By now it should be clear that the formatting capabilities in the .NET Framework were designed to offer you a great deal of flexibility and control. However, we’re not quite finished. It’s possible for you to define a method that StringBuilder’s AppendFormat method will call whenever any object is being formatted into a string. In other words, instead of calling ToString for each object, Append­ Format can call a function you define, allowing you to format any or all of the objects in any way you want. What I’m about to describe also works with String’s Format method.

Let me explain this mechanism by way of an example. Let’s say that you’re formatting HTML text that a user will view in an Internet browser. You want all Int32 values to appear in bold. To accom- plish this, every time an Int32 value is formatted into a String, you want to surround the string with HTML bold tags: <B> and </B>. The following code demonstrates how easy it is to do this.

 

using System; using System.Text;

using System.Threading;

 

public static class Program { public static void Main() {

StringBuilder sb = new StringBuilder();

sb.AppendFormat(new BoldInt32s(), "{0} {1} {2:M}", "Jeff", 123, DateTime.Now); Console.WriteLine(sb);

}

}

 

internal sealed class BoldInt32s : IFormatProvider, ICustomFormatter { public Object GetFormat(Type formatType) {

if (formatType == typeof(ICustomFormatter)) return this;

return Thread.CurrentThread.CurrentCulture.GetFormat(formatType);

}

 

public String Format(String format, Object arg, IFormatProvider formatProvider) { String s;

 

IFormattable formattable = arg as IFormattable;


if (formattable == null) s = arg.ToString();

else s = formattable.ToString(format, formatProvider);

 

if (arg.GetType() == typeof(Int32)) return "<B>" + s + "</B>";

return s;

}

}

 

When you compile and run this code where “en-US” is the thread’s current culture, it displays the

following output (your date may be different, of course).

 

Jeff <B>123</B> September 1

 

In Main, I’m constructing an empty StringBuilder and then appending a formatted string into it. When I call AppendFormat, the first parameter is an instance of the BoldInt32s class. This class implements the IFormatProvider interface that I discussed earlier. In addition, this class implements the ICustomFormatter interface.

 

public interface ICustomFormatter {

String Format(String format, Object arg, IFormatProvider formatProvider);

}

 

This interface’s Format method is called whenever StringBuilder’s AppendFormat needs to obtain a string for an object. You can do some pretty clever things inside this method that give you a great deal of control over string formatting. Let’s look inside the AppendFormat method to see exactly how it works. The following pseudocode shows how AppendFormat works.

 

public StringBuilder AppendFormat(IFormatProvider formatProvider, String format, params Object[] args) {

 

// If an IFormatProvider was passed, find out

// whether it offers an ICustomFormatter object. ICustomFormatter cf = null;

 

if (formatProvider != null) cf = (ICustomFormatter)

formatProvider.GetFormat(typeof(ICustomFormatter));

 

// Keep appending literal characters (not shown in this pseudocode)

// and replaceable parameters to the StringBuilder's character array. Boolean MoreReplaceableArgumentsToAppend = true;

while (MoreReplaceableArgumentsToAppend) {

// argFormat refers to the replaceable format string obtained

// from the format parameter String argFormat = /* ... */;

 

// argObj refers to the corresponding element

// from the args array parameter Object argObj = /* ... */;


// argStr will refer to the formatted string to be appended

// to the final, resulting string String argStr = null;

 

// If a custom formatter is available, let it format the argument. if (cf != null)

argStr = cf.Format(argFormat, argObj, formatProvider);

 

// If there is no custom formatter or if it didn't format

// the argument, try something else. if (argStr == null) {

// Does the argument's type support rich formatting? IFormattable formattable = argObj as IFormattable; if (formattable != null) {

// Yes; pass the format string and provider to

// the type's IFormattable ToString method.

argStr = formattable.ToString(argFormat, formatProvider);

} else {

// No; get the default format by using

// the thread's culture information.

if (argObj != null) argStr = argObj.ToString(); else argStr = String.Empty;

}

}

// Append argStr's characters to the character array field member.

/* ... */

 

// Check if any remaining parameters to format MoreReplaceableArgumentsToAppend = /* ... */;

}

return this;

}

 

When Main calls AppendFormat, AppendFormat calls my format provider’s GetFormat method, passing it the ICustomFormatter type. The GetFormat method defined in my BoldInt32s type sees that the ICustomFormatter is being requested and returns a reference to itself because it implements this interface. If my GetFormat method is called and is passed any other type, I call the GetFormat method of the CultureInfo object associated with the calling thread.

Whenever AppendFormat needs to format a replaceable parameter, it calls ICustomFormatter’s Format method. In my example, AppendFormat calls the Format method defined by my BoldInt­ 32s type. In my Format method, I check whether the object being formatted supports rich formatting via the IFormattable interface. If the object doesn’t, I then call the simple, parameterless ToString method (inherited from Object) to format the object. If the object does support IFormattable, I then call the rich ToString method, passing it the format string and the format provider.

Now that I have the formatted string, I check whether the corresponding object is an Int32 type, and if it is, I wrap the formatted string in <B> and </B> HTML tags and return the new string. If the object is not an Int32, I simply return the formatted string without any further processing.



Date: 2016-03-03; view: 717


<== previous page | next page ==>
Constructing aStringBuilder Object | Nbsp;   Parsing a String to Obtain an Object: Parse
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.018 sec.)