I have a custom formatter specified, basically like this:
public class NotationNumericFormatter : IFormatProvider, ICustomFormatter
{
public object GetFormat(Type formatType_)
{
return (formatType_ == typeof(ICustomFormatter) ? this : null;
}
public string Format(string format_, object arg_, IFormatProvider formatProvider_)
{
if (!Equals(formatProvider_) || arg_ == null) // <-- I put a breakpoint here...
{
return;
}
// then a bunch of stuff happens here.
}
}
What's stumping me at the moment is, the following code:
// _myFormatter is a NotationNumericFormatter which gets instanced
// in the ctor of the class in question.
var result = string.Format(_myFormatter, (parameter_ ?? "").ToString(), value_);
Which is never, ever, hitting the first line in my formatter's Format() method. What am I missing here? Is there some subtlety to string.Format that I'm missing?
If parameter does not have {0} then the formatter won't break point
this will breakpoint
var result = string.Format(_myFormatter, "{0}", value_);
this won't
var result = string.Format(_myFormatter, "", value_);
When you call String.Format(IFormatProvider provider , String format , params Object[] args ) the second parameter format hast to specified as a valid format string i.e. it cannot be empty.
Try to set parameter_ to something like "{0}" then it should work.
Related
String formatting in C#;
Can I use it? Yes.
Can I implement custom formatting? No.
I need to write something where I can pass a set of custom formatting options to string.Format, which will have some effect on the particular item.
at the moment I have something like this:
string.Format("{0}", item);
but I want to be able to do things with that item:
string.Format("{0:lcase}", item); // lowercases the item
string.Format("{0:ucase}", item); // uppercases the item
string.Format("{0:nospace}", item); // removes spaces
I know I can do things like .ToUpper(), .ToLower() etc. but I need to do it with string formatting.
I've been looking into things like IFormatProvider and IFormattable but I don't really know if they are the things I should be using, or, how to implement them.
Can anyone explain how I can solve this problem?
Rationale (just in case you want to know...)
I have a small program, where I can enter a comma delimited set of values, and a template. The items are passed into string.Format, along with the template which creates an output. I want to provide template formatting options, so that the user can control how they want items to be output.
You can make a custom formatter, something like:
public class MyFormatter : IFormatProvider, ICustomFormatter
{
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
return this;
else
return null;
}
public string Format(string fmt, object arg, IFormatProvider formatProvider)
{
if(arg == null) return string.Empty;
if(fmt == "lcase")
return arg.ToString().ToLower();
else if(fmt == "ucase")
return arg.ToString().ToUpper();
else if(fmt == "nospace")
return arg.ToString().Replace(" ", "");
// Use this if you want to handle default formatting also
else if (arg is IFormattable)
return ((IFormattable)arg).ToString(fmt, CultureInfo.CurrentCulture);
return arg.ToString();
}
}
Then use it like:
string.Format(new MyFormatter(),
"{0:lcase}, {0:ucase}, {0:nospace}",
"My Test String")
This should return:
my test string, MY TEST STRING, MyTestString
I have to manage a particular string formatting/padding condition. To be short I need to pad a string argument only if the argument length is 0. If I use the typical aligment parameter the padding is made if the length of the argument is smaller then the provided alignment value. For example:
string format = "#{0,10}#";
string argument;
string output;
argument = Console.ReadLine();
output = String.Format(format, argument);
String.WriteLine(output);
If I enter "try" as a value I got this result:
#try #
If I enter "trytrytrytry" I got:
#trytrytrytry#
What I would like to happen is depicted below:
Entering "" I would like to have:
# #
but entering "try" I would like to have:
#try#
The code I'm going to write should be as much generic as possibile since the format parameter is not static and is defined at runtime.
The best practice to do this is to define a custom string formatter. Unluckly it seems that the customization code can only act on the 'format' portion of the format parameter of the String.Format() method.
Indeed If I define a custom formatter:
public class EmptyFormatter : IFormatProvider, ICustomFormatter
{
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
return this;
else
return null;
}
public string Format(string format, object arg, IFormatProvider formatProvider)
{
if (!this.Equals(formatProvider))
return null;
if (string.IsNullOrEmpty(format))
throw new ArgumentNullException();
return numericString;
}
}
The format and arg parameters of the Format method didn't contain the alignment parameter then I cannot actually check what lenght of the padding value should be applied and therefore I cannot act properly. Moreover this part of the 'formatting' of the string seems to be applied somewhere else but I have not idea where.
Is there a way to 'alter' this behaviour ?
A format item has the following syntax:
index[,alignment][ :formatString] }
The reason the format parameter is null is because there is no formatString component, there is only alignment. I couldn't find a way to access the alignment component from the Format method. However, one (ugly) solution is to set the formatString to be the same as the alignment, so that you can access it using the format parameter:
#{0,10:10}#
A less ugly solution would be to create your own method extension that first extracts the alignment from the given format and then calls the traditional String.Format method.
For example:
public static string StringFormat(this string Arg, string Format) {
//extract alignment from string
Regex reg = new Regex(#"{[-+]?\d+\,[-+]?(\d+)(?::[-+]?\d+)?}");
Match m = reg.Match(Format);
if (m != null) { //check if alignment exists
int allignment = int.Parse(m.Groups[1].Value); //get alignment
Arg = Arg.PadLeft(allignment); //pad left, you can make the length check here
Format = Format.Remove(m.Groups[1].Index - 1, m.Groups[1].Length + 1); //remove alignment from format
}
return (string.Format(Format, Arg));
}
To use the code:
string format = "#{0,10}#";
string argument = "try";
string output = argument.StringFormat(format); //"# try#"
This situation has always nagged me. Just as an example, suppose a console application expects filepath in as a command line argument.
string first = args[0];
but if there are no arguments, then an error will occur. I suppose I could do something like the following:
string first = (args[0]!=null) ? args[0] : "c:\";
What I'm looking for is something a bit more elegant like:
string first = (MyTryParse(args[0],"c:\");
Which I could write as an extension, however that won't work because args[0] will throw an exception before the method can be called.
Also check, if args[0] is null:
public string MyTryParse(string[] args, int index, string defaultVal)
{
return index < args.Length ? (args[index] ?? defaultVal) : defaultVal
}
...
string first = MyTryParse(args, 0, "c:\");
Same approach but using extension method,
public static class Extensioin
{
public static string MyTryParse(this string[] args, string defaultVal)
{
return args.Length > 0 ? args[0] : defaultVal;
}
}
And using above method something like this,
string first = args.MyTryParse(#"c:\");
Pass args instead of args[0]
Try like this
public string MyTryParse(string[] args, string defaultVal) {
return args.Length > 0 ? args[0] : defaultVal
}
LINQ already has DefaultIfEmpty method for that purpose:
string first = args.DefaultIfEmpty("c:\\").First();
I was wondering if there's a syntax for formatting NULL values in string.Format, such as what Excel uses
For example, using Excel I could specify a format value of {0:#,000.00;-#,000.00,NULL}, which means display the numeric value as number format if positive, number format in parenthesis if negative, or NULL if the value is null
string.Format("${0:#,000.00;(#,000.00);NULL}", someNumericValue);
Edit
I'm looking for formatting NULL/Nothing values for all data types, not just numeric ones.
My example is actually incorrect because I mistakenly thought Excel used the 3rd parameter if the value was NULL, but it's actually used when the value is 0. I'm leaving it in there because it's the closest thing I can think of to what I was hoping to do.
I am hoping to avoid the null coalescing operator because I am writing log records, and the data is not usually a string
It would be much easier to write something like
Log(string.Format("Value1 changes from {0:NULL} to {1:NULL}",
new object[] { oldObject.SomeValue, newObject.SomeValue }));
than to write
var old = (oldObject.SomeValue == null ? "null" : oldObject.SomeValue.ToString());
var new = (newObject.SomeValue == null ? "null" : newObject.SomeValue.ToString());
Log(string.Format("Value1 changes from {0} to {1}",
new object[] { old, new }));
You can define a custom formatter that returns "NULL" if the value is null and otherwise the default formatted string, e.g.:
foreach (var value in new[] { 123456.78m, -123456.78m, 0m, (decimal?)null })
{
string result = string.Format(
new NullFormat(), "${0:#,000.00;(#,000.00);ZERO}", value);
Console.WriteLine(result);
}
Output:
$123.456,78
$(123.456,78)
$ZERO
$NULL
Custom Formatter:
public class NullFormat : IFormatProvider, ICustomFormatter
{
public object GetFormat(Type service)
{
if (service == typeof(ICustomFormatter))
{
return this;
}
else
{
return null;
}
}
public string Format(string format, object arg, IFormatProvider provider)
{
if (arg == null)
{
return "NULL";
}
IFormattable formattable = arg as IFormattable;
if (formattable != null)
{
return formattable.ToString(format, provider);
}
return arg.ToString();
}
}
I don't think there's anything in String.Format that will let you specify a particular format for null strings. A workaround is to use the null-coalescing operator, like this:
const string DefaultValue = "(null)";
string s = null;
string formatted = String.Format("{0}", s ?? DefaultValue);
Is this what you want?
string test;
test ?? "NULL"
It looks like String.Format for .NET acts the same way as Excel, i.e., you can use ; separator for positive, negative, and 0 values, but not NULL: http://msdn.microsoft.com/en-us/library/0c899ak8.aspx#SectionSeparator.
You will probably just have to handle the null value manually:
if (myval == null)
// handle
else
return String.Format(...);
You could use an extension method:
public static string ToDataString(this string prm)
{
if (prm == null)
{
return "NULL";
}
else
{
return "'" + prm.Replace("'", "''") + "'";
}
}
Then in your code you can do:
string Field1="Val";
string Field2=null;
string s = string.Format("Set Value:{0}, NullValue={1}",Field1.ToDataString(), Field2.ToDataString());
I'm using a closed-source third-party library like this:
object val = SomeClass.ExtractValue( someObject );
Now somewhere further down the road, the third-party library tries to parse a DateTime value that has an unexpected format and throws a FormatException.
In this case, I would like to retrieve the string that it hasn't succeeded to parse and try to parse it myself.
Something like this:
object val;
try
{
val = SomeClass.ExtractValue( someObject );
}
catch( FormatException e )
{
string failed = e.GetParseArgument( );
val = DateTime.Parse( failed + " 2010" );
}
Yes, simply appending the year is pretty pointless, but you get the idea.
The third-party library doesn't support all formats I need, but I can't easily get the data from "someObject" either.
(Yes, I could try to replicate what the library does using Reflector, but I'd like to avoid that.)
Is there any way to do this?
Thanks.
Since someObject is an IDataReader, you could create a decorator and pass that into ExtractValue. You could then intercept the date string and modify the format before it gets passed to the library e.g.
public class DateFormattingDataReader : IDataReader
{
private readonly IDataReader inner;
public DateFormattingDataReader(IDataReader inner)
{
this.inner = inner;
}
public string GetString(int index)
{
string s = this.inner.GetString(index);
if(index == problematicColumnIndex)
{
//try to parse string and then format it for the library
}
else return s;
}
}
Alternatively you could record all values read from the reader, then you can get the failing data as the last read item and try to parse it yourself.