Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET: Is there a String.Format form for inserting the value of an object property into a string?

I think the direct answer to the question is 'No' but I'm hoping that someone has written a real simple library to do this (or I can do it...ugh...)

Let me demonstrate what I am looking for with an example. Suppose I had the following:

class Person {
  string Name {get; set;}
  int NumberOfCats {get; set;}
  DateTime TimeTheyWillDie {get; set;}
}

I would like to be able to do something like this:

static void Main() {
  var p1 = new Person() {Name="John", NumberOfCats=0, TimeTheyWillDie=DateTime.Today};
  var p2 = new Person() {Name="Mary", NumberOfCats=50, TimeTheyWIllDie=DateTime.Max};

  var str = String.Format(

"{0:Name} has {0:NumberOfCats} cats and {1:Name} has {1:NumberOfCats} cats.  They will die {0:TimeTheyWillDie} and {1:TimeTheyWillDie} respectively
", p1, p2);

  Console.WriteLine(str);
}

Does anyone know if theres a format for doing something like this or if someone has written a library to do it? I know it shouldn't be too hard, but I'd rather not be reimplementing the wheel.

like image 465
George Mauer Avatar asked Dec 10 '08 20:12

George Mauer


4 Answers

Edit: You don't have to implement IFormattable for each object...that'd be a PITA, severely limiting, and a fairly large maintenance burden. Just use Reflection and a IFormatProvider with ICustomFormatter and it'll work with any object. String.Format has an overload to take one as a parameter.

I've never thought of this before, but you intrigued me - so I had to give it a quick whirl. Note that I chose to allow an additional format string to be passed to the property value, and that it only works with non indexed and accessible properties (though you could easily add that).

public class ReflectionFormatProvider : IFormatProvider, ICustomFormatter {
    public object GetFormat(Type formatType) {
        return formatType == typeof(ICustomFormatter) ? this : null;
    }

    public string Format(string format, object arg, IFormatProvider formatProvider) {
        string[] formats = (format ?? string.Empty).Split(new char[] { ':' }, 2);
        string propertyName = formats[0].TrimEnd('}');
        string suffix = formats[0].Substring(propertyName.Length);
        string propertyFormat = formats.Length > 1 ? formats[1] : null;

        PropertyInfo pi = arg.GetType().GetProperty(propertyName);
        if (pi == null || pi.GetGetMethod() == null) {
            // Pass thru
            return (arg is IFormattable) ? 
                ((IFormattable)arg).ToString(format, formatProvider) 
                : arg.ToString();
        }

        object value = pi.GetGetMethod().Invoke(arg, null);
        return (propertyFormat == null) ? 
            (value ?? string.Empty).ToString() + suffix
            : string.Format("{0:" + propertyFormat + "}", value);
    }
}

And your slightly modified example:

var p1 = new Person() {Name="John", NumberOfCats=0, TimeTheyWillDie=DateTime.Today};
var p2 = new Person() {Name="Mary", NumberOfCats=50, TimeTheyWillDie=DateTime.MaxValue};

var str = string.Format(
    new ReflectionFormatProvider(),
    @"{0:Name} has {0:NumberOfCats} cats and {1:Name} has {1:NumberOfCats} cats. 
    They will die {0:TimeTheyWillDie:MM/dd/yyyy} and {1:TimeTheyWillDie} respectively.
    This is a currency: {2:c2}.", 
    p1, 
    p2,
    8.50M
);

Console.WriteLine(str);

Outputs:

John has 0 cats and Mary has 50 cats. 
They will die 12/10/2008 and 12/31/9999 11:59:59 PM respectively.
This is a currency: $8.50.
like image 127
Mark Brackett Avatar answered Sep 19 '22 20:09

Mark Brackett


You could override the ToString() for your class.

Good Article here

like image 28
JamesSugrue Avatar answered Sep 17 '22 20:09

JamesSugrue


What is after the ":" is passed as an argument to the ToString method of your class.
Just declare a ToString method accepting a string, and the 'Name', 'NumberOfCats' etc. will be passed in that parameter.

EDIT: You must implement System.IFormattable. This works:

class Person : IFormattable
{
    public override string ToString()
    {
        return "Person";
    }

    public string ToString(string format, IFormatProvider formatProvider)
    {
        if (format == "Name")
        {
            return "John";
        }
        if (format == "NumberOfCats")
        {
            return "12";
        }
        return "Unknown format string";
    }

}

class Program
{
    static void Main(string[] args)
    {
        Person p = new Person();
        Console.WriteLine(string.Format("Name = {0:Name}",p));
        Console.WriteLine(string.Format("NumberOfCats = {0:NumberOfCats}", p));
    }
}
like image 42
Paolo Tedesco Avatar answered Sep 19 '22 20:09

Paolo Tedesco


Check out my library "Expansive"

On Nuget here: http://nuget.org/List/Packages/Expansive

On GitHub here: http://github.com/anderly/expansive

like image 44
anderly Avatar answered Sep 20 '22 20:09

anderly