FormatWith - inne implementacje

25 stycznia 2010, 00:43

KomentarzeKomentarze: 5 KategorieKategorie: tips.net

Łukasz Kurzyniec wspomniał o bardzo fajnym sposobie formatowania stringa, gdzie zamiast kolejnych indeksów {0}, {1}, itd podaje się nazwy właściwości, a jako źródło obiekt je posiadający. Niestety nie mogłem dodać komentarza na blogu Łukasza, stąd ten post.

Swego czasu Phill Haack zainteresował się tym tematem i pokazał inne implementacje. Obecnie dzięki Niemu używam implementacji Henri'ego, która to mimo większej ilości kodu okazała się najszybsza.

Dodatkowo w swojej implementacji dodałem jedno przeciążenie, dzięki któremu możemy określi czy brak właściwości odpowiadającej znalezionemu parametrowi ma wyrzucić wyjątek czy też nie. Przydatne przy bardziej zaawansowanym formatowaniu/parsowaniu.

Poniżej przeklejam kod, aby nigdzie się nie zgubił ;) Acha, u mnie ta metoda nazywa się FormatNamed.

   1:  public static string FormatNamed(this string format, object source)
   2:  {
   3:      return FormatNamed(format, source, false);
   4:  }
   5:   
   6:  public static string FormatNamed(this string format, object source, bool ignoreNotFound)
   7:  {
   8:      if (format == null)
   9:      {
  10:          throw new ArgumentNullException("format");
  11:      }
  12:   
  13:      StringBuilder result = new StringBuilder(format.Length * 2);
  14:   
  15:      using (var reader = new StringReader(format))
  16:      {
  17:          StringBuilder expression = new StringBuilder();
  18:          int @char = -1;
  19:   
  20:          State state = State.OutsideExpression;
  21:          do
  22:          {
  23:              switch (state)
  24:              {
  25:                  case State.OutsideExpression:
  26:                      @char = reader.Read();
  27:                      switch (@char)
  28:                      {
  29:                          case -1:
  30:                              state = State.End;
  31:                              break;
  32:                          case '{':
  33:                              state = State.OnOpenBracket;
  34:                              break;
  35:                          case '}':
  36:                              state = State.OnCloseBracket;
  37:                              break;
  38:                          default:
  39:                              result.Append((char)@char);
  40:                              break;
  41:                      }
  42:                      break;
  43:                  case State.OnOpenBracket:
  44:                      @char = reader.Read();
  45:                      switch (@char)
  46:                      {
  47:                          case -1:
  48:                              throw new FormatException();
  49:                          case '{':
  50:                              result.Append('{');
  51:                              state = State.OutsideExpression;
  52:                              break;
  53:                          default:
  54:                              expression.Append((char)@char);
  55:                              state = State.InsideExpression;
  56:                              break;
  57:                      }
  58:                      break;
  59:                  case State.InsideExpression:
  60:                      @char = reader.Read();
  61:                      switch (@char)
  62:                      {
  63:                          case -1:
  64:                              throw new FormatException();
  65:                          case '}':
  66:                              result.Append(OutExpression(source, expression.ToString(), ignoreNotFound));
  67:                              expression.Length = 0;
  68:                              state = State.OutsideExpression;
  69:                              break;
  70:                          default:
  71:                              expression.Append((char)@char);
  72:                              break;
  73:                      }
  74:                      break;
  75:                  case State.OnCloseBracket:
  76:                      @char = reader.Read();
  77:                      switch (@char)
  78:                      {
  79:                          case '}':
  80:                              result.Append('}');
  81:                              state = State.OutsideExpression;
  82:                              break;
  83:                          default:
  84:                              throw new FormatException();
  85:                      }
  86:                      break;
  87:                  default:
  88:                      throw new InvalidOperationException("Invalid state.");
  89:              }
  90:          } while (state != State.End);
  91:      }
  92:   
  93:      return result.ToString();
  94:  }
  95:   
  96:  private static string OutExpression(object source, string expression, bool ignoreNotFound)
  97:  {
  98:      string format = "";
  99:      string orygexpression = expression;
 100:   
 101:      int colonIndex = expression.IndexOf(':');
 102:      if (colonIndex > 0)
 103:      {
 104:          format = expression.Substring(colonIndex + 1);
 105:          expression = expression.Substring(0, colonIndex);
 106:      }
 107:   
 108:      try
 109:      {
 110:          if (String.IsNullOrEmpty(format))
 111:          {
 112:              return (DataBinder.Eval(source, expression) ?? "").ToString();
 113:          }
 114:          return DataBinder.Eval(source, expression, "{0:" + format + "}") ?? "";
 115:      }
 116:      catch (HttpException)
 117:      {
 118:          if (!ignoreNotFound)
 119:          {
 120:              throw new FormatException();
 121:          }
 122:          else
 123:          {
 124:              return orygexpression;
 125:          }
 126:      }
 127:  }
 128:   
 129:  private enum State
 130:  {
 131:      OutsideExpression,
 132:      OnOpenBracket,
 133:      InsideExpression,
 134:      OnCloseBracket,
 135:      End
 136:  }

 

Ten artykuł jest widoczny także na: ZINE.NET

Jeśli spodobał Ci się ten artykuł, to podziel się tą informacją z innymi.

Gravatar
Łukasz K.
2010-01-25 22:03

Dziękuję za zauważenie :)

Gravatar
dario-g
2010-01-25 23:33

A nie ma za co :)

Gravatar
Wojciech Gebczyk
2010-01-27 13:46

Albo mozna wpiac sie w infrastrulture formatowanie we Frameworku i polaczyc String.Format("... {0} ...", ...) i pisac tak:

var str1 = String.Format(fmt, "pt = [ x: {0}; y: {1}; z: {2} ]", 1, 2M, 333.55);

Debug.Assert(str1 == "pt = [ x: 1; y: 2; z: 333.55 ]");

var str2 = String.Format(fmt, "Person: {0:LastName}, {0:FirstName} ({0:BirthDate,yyyy-MM-dd}) | {1} | {2:,0.0000}", Person.Sample1, true, 123.55);

Debug.Assert(str2 == "Person: Kowalski, Jan (1990-02-03) | True | 123.5500");

gdzie Sample1 = new Person { FirstName = "Jan", LastName = "Kowalski", BirthDate = new DateTime(1990, 2, 3), }

ma to zalete ze maly kod - mozna uzywac prostych typow itd...

Do skomplikowachych szablonow tekstorych jest pare rozwiazan na sieci.

a custom formatter z grubsza tak wygladac moze:

-----------------------------------------------------

using System;

using System.Diagnostics;

static class EntryPoint

{

static void Main()

{

IFormatProvider fmt = new PropertyCustomFormatter();

var str1 = String.Format(fmt, "pt = [ x: {0}; y: {1}; z: {2} ]", 1, 2M, 333.55);

Debug.Assert(str1 == "pt = [ x: 1; y: 2; z: 333.55 ]");

var str2 = String.Format(fmt, "Person: {0:LastName}, {0:FirstName} ({0:BirthDate,yyyy-MM-dd}) | {1} | {2:,0.0000}", Person.Sample1, true, 123.55);

Debug.Assert(str2 == "Person: Kowalski, Jan (1990-02-03) | True | 123.5500");

}

}

public sealed class Person

{

public string FirstName { get; set; }

public string LastName { get; set; }

public DateTime BirthDate { get; set; }

public static readonly Person Sample1 = new Person { FirstName = "Jan", LastName = "Kowalski", BirthDate = new DateTime(1990, 2, 3), };

public static readonly Person Sample2 = new Person { FirstName = "John", LastName = "Doe", BirthDate = new DateTime(1966, 5, 5), };

}

public sealed class PropertyCustomFormatter : ICustomFormatter, IFormatProvider

{

public string Format(string format, object arg, IFormatProvider formatProvider)

{

if (String.IsNullOrEmpty(format))

{ return ToStringInternal(format, arg, formatProvider); }

if (arg == null) { throw new ArgumentException("arg"); }

string propertyName = format;

string propertyFormatter = null;

var separatorIndex = format.IndexOf(',');

if (separatorIndex != -1)

{

propertyName = format.Substring(0, separatorIndex);

propertyFormatter = format.Substring(separatorIndex + 1);

}

var valueToFormat = arg;

if (!String.IsNullOrEmpty(propertyName))

{ valueToFormat = arg.GetType().GetProperty(propertyName).GetValue(arg, null); }

return ToStringInternal(propertyFormatter, valueToFormat, formatProvider);

}

private static string ToStringInternal(string propertyFormatter, object valueToFormat, IFormatProvider formatProvider)

{

string result;

if (valueToFormat is IFormattable)

{ result = ((IFormattable)valueToFormat).ToString(propertyFormatter, formatProvider); }

else

{ result = valueToFormat.ToString(); }

if (result == null)

{ result = String.Empty; }

return result;

}

public object GetFormat(Type formatType) { return this; }

}

-----------------------------------------------------

Gravatar
dario-g
2010-01-27 14:02

Oczywiście można i tak, ale jak to się ma do wydajności? :)

Przedstawiane rozwiązania są właśnie pisane pod kątem szybkości działania.

Gravatar
Wojciech Gebczyk
2010-01-27 14:29

Hmm... nie wiem czy dobrze uzylem tego FormatNamed(...), ale wyniki ponizej:

option1: 1264 ms

option2: 2810 ms

wiec widac ze podobne rozwiazania sa - nie ma strasznej rozbieznosci.

a tak testowalem

-------

IFormatProvider fmt = new PropertyCustomFormatter();

Func<Person, string> option1 = p => String.Format(fmt, "Person: {0:LastName}, {0:FirstName} ({0:BirthDate})", p);

Func<Person, string> option2 = p => "Person: {LastName}, {FirstName} ({BirthDate})".FormatNamed(p);

var option1Watch = Stopwatch.StartNew();

for (int i = 0; i < 100000; i++)

{

option1(Person.Sample1);

}

option1Watch.Stop();

var option2Watch = Stopwatch.StartNew();

for (int i = 0; i < 100000; i++)

{

option2(Person.Sample1);

}

option2Watch.Stop();

Console.WriteLine("option1: {0} ms", option1Watch.ElapsedMilliseconds);

Console.WriteLine("option2: {0} ms", option2Watch.ElapsedMilliseconds);

Dodaj komentarz Dodaj komentarz

Twój email (niepublikowany/opcjonalnie):

Twoja strona (opcjonalnie):

Imię i nazwisko/nick (wymagane):

Treść (wymagane):

Chcę być poinformowany o kolejnych komentarzach:

Te pola zostaw puste

Uwaga! Zastrzegam sobie prawo do usuwania obraźliwych i wulgarnych komentarzy.