2016-04-05 12 views
2

Ben izin ikame değerler sınıf var:Çalışma zamanında Interpolated [Adlandırıldı] değerleriyle String.Format gibi davranışlar nasıl alınır?

class MaskDictionary 
{ 
    public int id { get; set; } 
    public string last { get; set; } 
    public string lastinitial { get; set; } 
    public string first { get; set; } 
    public string firstinitial { get; set; } 
    public string salutation { get; set; } 
    public DateTime today { get; set; } 
} 

ve ben gibi, kullanıcı girişi gibi bir biçimlendirme dizesi almak istiyorum:

string userFormat = "{last}, {first} {today}"; 

ve aralara değer oluşturmak.

string.Format("{last}, {first} {today}", MaskDictionary); 

ancak girdi dizesi dinamik hale başarısız ve: Kavramsal olarak benzer

çalışma zamanı biçimlendirme sağlamak için basit, temiz yolu nedir
string.Format(userFormat, MaskDictionary); 

?

 string userFormat = "{last}, {first} {today}"; 
     PropertyInfo[] properties = typeof(MaskDictionary).GetProperties(); 
     foreach (PropertyInfo property in properties) 
     { 
      userFormat = string.Replace(property.name, property.GetValue(mask)); 
     } 

gibi yansıma ve özyinelemeli yerine geçer kullanan bazı aksak seçenekleri vardır ama daha iyi bir yolu olmalı. answers--

karşılaştırılması

--update ben performansı için cevaplar iki çözüm önerilerini test edilmiş ve oldukça şaşırtıcı sonuçlar aldık.

static class Format2 
    { 
     static public string Format(string format, MaskDictionary md) 
     { 
      string val = format; 
      foreach (PropertyInfo property in typeof(MaskDictionary).GetProperties()) 
      { 
       val = val.Replace("{" + property.Name + "}", property.GetValue(md).ToString()); 
      } 
      return val; 
     } 
    } 
static class Format1 
{ 
    public static string FormatWith(this string format, IFormatProvider provider, object source) 
    { 
     if (format == null) 
      throw new ArgumentNullException("format"); 

     Regex r = new Regex(@"(?<start>\{)+(?<property>[\w\.\[\]]+)(?<format>:[^}]+)?(?<end>\})+", 
      RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); 

     List<object> values = new List<object>(); 
     string rewrittenFormat = r.Replace(format, delegate (Match m) 
     { 
      Group startGroup = m.Groups["start"]; 
      Group propertyGroup = m.Groups["property"]; 
      Group formatGroup = m.Groups["format"]; 
      Group endGroup = m.Groups["end"]; 

      values.Add((propertyGroup.Value == "0") 
       ? source 
       : DataBinder.Eval(source, propertyGroup.Value)); 

      return new string('{', startGroup.Captures.Count) + (values.Count - 1) + formatGroup.Value 
       + new string('}', endGroup.Captures.Count); 
     }); 

     return string.Format(provider, rewrittenFormat, values.ToArray()); 
    } 
} 

Regex çözümü daha yavaş, çok daha yavaştır. 5 özellikli bir sözlük nesnesi ve bir 105 özellik sözlüğü nesnesine sahip bir kısa biçim dizesinin (20 karakter, 3 yer değiştirme) 1000 yineleme ve uzun biçim dizgisi (2000 karakter, 3 yer değiştirme) ve uzun bir sözlük nesnesi kullanıyorum. sonuçlar:

kısa biçim, küçük sözlüğü
Regex - 2150 msn
değiştirin - 3 ms
kısa biçim, büyük sözlüğü
Regex - 2160 msn
değiştirin - 30 ms
Uzun biçim, kısa Sözlük
Regex - 2170 ms
değiştirin - 26 ms
Uzun biçim, büyük sözlüğü
Regex - 2250 msn
değiştirin - 330 ms

büyük sözlükle de ölçek değildir değiştirin ama çok daha hızlı o aldığını başlar Büyük bir sözlük artı çok uzun bir biçim dizesi daha yavaş olacak. 105 özellik sözlüğü ile, işlemek için aynı miktarda almak için yaklaşık 16.000 karakter dizisi dizesi, ~ 2500ms aldı. 5 özellik küçük sözlük ile, regex asla hızlı değildi. 600K karakter formatlı bir dize regex için 14000 ms ve yerine geçmek için 7000 ms aldı ve 1.7M karakter formatı dizisi 38000 ms'ye karşılık 21000 ms aldı. Sözlük nesnesi makul boyutta ve biçim dizgesi 80 sayfadan kısa olduğu sürece kazanımları değiştirin.

+0

Bunun gibi zarif bir şey yok ... –

cevap

1

:

using System.Reflection; 
using System.ComponentModel; 
class MaskDictionary 
{ 
    // ... properties ... 

    public string ToString(string format) 
    { 
     string val = format; 
     foreach (PropertyInfo property in typeof(MaskDictionary).GetProperties()) 
     { 
      val = val.Replace("{" + property.Name + "}", property.GetValue(this).ToString()); 
     } 
     return val; 
    } 
} 

Düzenleme: burada özelliğini yeniden adlandırma olmadan kullanıcı biçimi etiketlerini yeniden adlandırmak sağlayan bir sürümü:

class MaskDictionary 
{ 
    // ... properties ... 
    [DisplayName("bar")] 
    public string foo {get;set;} 
    public int baz {get;set;} 

    public string ToString(string format) 
    { 
     string val = format; 
     foreach (PropertyInfo property in typeof(MaskDictionary).GetProperties()) 
     { 
      var dispAttr = (DisplayNameAttribute)Attribute.GetCustomAttribute(property, typeof(DisplayNameAttribute)); 
      string pName = dispAttr != null ? dispAttr.DisplayName : property.Name; 
      val = val.Replace("{" + pName + "}", property.GetValue(this).ToString()); 
     } 
     return val; 
    } 
} 

kullanımı:

var m = new MaskDictionary(); 
m.foo = "hello"; 
m.baz = 111; 
Console.WriteLine(m.ToString("{foo} {bar} {baz}")); 
//output: {foo} hello 111 
1

çerçevesinde şey yok, ama adına göre biçim dizesine özelliklerini enjekte etmek kullanabileceği bir akıllı extension method var: Dahilisi woithout alabilirsiniz yakın dize interpolasyon kullanmaktır

string result = "{last}, {first} {today}".FormatWith(MaskDictionary); 

C# 6:

string result = $"{MaskDictionary.last}, {MaskDictionary.first} {MaskDictionary.today}"; 
+0

Örneğiniz yalnızca derleme zamanında çalışır. Dize sonucu kullanıcı girdisinden veya başka bir çalışma zamanı kaynağından geliyorsa, dize enterpolasyonu başarısız olur. – user15741

+0

@ user15741 _second_ örneği, bir derleme zamanı yapısıdır, doğrudur. İlk örnek, çalışma zamanında bir dize alabilir –

2

James Newton King (JSON adam) bir FormatWith() uzatma yöntemi esasen yapmaya çalışıyordunuz ne başarmak olacağını in this blog post tanımlanan kullanır:

public static string FormatWith(this string format, IFormatProvider provider, object source) 
{ 
    if (format == null) 
    throw new ArgumentNullException("format"); 

    Regex r = new Regex(@"(?<start>\{)+(?<property>[\w\.\[\]]+)(?<format>:[^}]+)?(?<end>\})+", 
    RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); 

    List<object> values = new List<object>(); 
    string rewrittenFormat = r.Replace(format, delegate(Match m) 
    { 
    Group startGroup = m.Groups["start"]; 
    Group propertyGroup = m.Groups["property"]; 
    Group formatGroup = m.Groups["format"]; 
    Group endGroup = m.Groups["end"]; 

    values.Add((propertyGroup.Value == "0") 
     ? source 
     : DataBinder.Eval(source, propertyGroup.Value)); 

    return new string('{', startGroup.Captures.Count) + (values.Count - 1) + formatGroup.Value 
     + new string('}', endGroup.Captures.Count); 
    }); 

    return string.Format(provider, rewrittenFormat, values.ToArray()); 
} 

Temelde fiili eşleme ve değiştirme işlemlerini gerçekleştirirken işlemek için .NET Databinder sınıfıyla birlikte Düzenli ifadeler dayanır.

Bir ToString (...) fonksiyonu içine kodunuzu dönüştürebilirsiniz
0

Dize uzantısı yöntemleri (FormatWith()) ile bunu yapan FormatWith adlı bir kitaplığım var. James Newton King uygulamasına benzer, ancak bazı avantajları vardır:

  • Regex yok. Uzantılar, giriş dizesini işlemek için bir durum makinesi ayrıştırıcısı kullanır; bu, daha hızlıdır ve kaçan parantezleri doğru şekilde işler.

  • DataBinder'a güvenmiyor. DataBinder, .NET Core'da mevcut değildir ve ayrıca, dengesiz girdiyle birlikte kullanıldığında da tehlikelidir.

  • .NET Standard 2.0'ı uygulayan her şeyle çalışır, bu nedenle .NET Core ve ASP.NET Core uygulamalarında kullanılabilir.

İlgili konular