C #에서 명명 된 문자열 형식


156

C #에서 위치가 아닌 이름으로 문자열을 형식화하는 방법이 있습니까?

파이썬에서는이 예제와 같은 것을 할 수 있습니다. 여기 ).

>>> print '%(language)s has %(#)03d quote types.' % \
      {'language': "Python", "#": 2}
Python has 002 quote types.

C #에서 이것을 수행하는 방법이 있습니까? 예를 들어 :

String.Format("{some_variable}: {some_other_variable}", ...);

변수 이름을 사용 하여이 작업을 수행 할 수 있으면 좋지만 사전도 허용됩니다.


나는 루비에서도 이것을 놓쳤다.
JesperE

나는 당신의 모범이 너무 단순하고 사람들이 당신에게 도움이되지 않는 답변을하도록 이끌고 있다고 생각합니다. 문자열에서 변수를 두 번 이상 사용하는 것이 더 실증적 일 수 있습니다.
웨지

실제로 SPECIFIC 혼란은 String.Format을 사용하는 것입니다. 그것은 내 자신과 같은 대답에 적합합니다. 변수 지향적이지 않기 때문에 도움이되지 않지만 String.Format에 관한 한 정확합니다.
John Rudy

1
String.Format에 대한 호출은 분명히 고안된 예입니다. 물론 타원으로 String.Format을 호출하는 것은 불가능하다는 것을 알지 못한다면. 문제는 위치가 아닌 명명 된 매개 변수로 포맷팅이 일어나기를 원치 않았다는 것입니다.
Jason Baker

참고 :이를 MS Connect의 사용자 음성에 제출하여 프레임 워크의 표준 기능이되도록 요청합니다. 관심이 있으신 분은 upvote하십시오 : visualstudio.uservoice.com/forums/121579-visual-studio/…
JohnLBevan

답변:


130

이를 처리하기위한 기본 제공 방법이 없습니다.

한 가지 방법이 있습니다

string myString = "{foo} is {bar} and {yadi} is {yada}".Inject(o);

여기 또 다른

Status.Text = "{UserName} last logged in at {LastLoginDate}".FormatWith(user);

Phil Haack에서 위의 두 가지를 부분적으로 기반으로하는 세 번째 개선 된 방법


11
FormatWith ()를 사용하여 매우 기뻤지 만 최근에 발생한 문제를 지적하고 싶었습니다. 이 구현은 SQL CLR에서 지원되지 않는 System.Web.UI의 DataBinder를 사용합니다. Inject (o)는 데이터 바인더에 의존하지 않으므로 SQL CLR 객체에서 멀티 토큰 대체에 유용합니다.
EBarr

1
답의 첫 문장을 업데이트 할 수 있습니다. 문자열 보간 은 C # 및 VB 에 몇 달 동안 존재합니다 (최종 ...). 귀하의 답변이 맨 위에 있으므로 업데이트 된 .NET 리소스에 링크를 연결할 수 있으면 독자에게 유용 할 수 있습니다.
miroxlav

1
@ miroxlav 그것은 실제로 동일하지 않습니다. 보간 문자열을 전달할 수 없습니다. stackoverflow.com/q/31987232/213725
DixonD

@DixonD – 당신은 확실히 맞지만 그들의 목적은 아니 었습니다. 연결 한 Q & A에서 OP는 변수 이름이 존재하기 전에 아직 변수 이름을 참조하려고합니다. 좋은 생각은 아니지만 누군가가 주장하면 특수 파서를 만들 수 있습니다. 그러나 일반적인 문자열 보간 개념으로 이것을 망칠 수는 없습니다.
miroxlav

44

방금 블로그에 게시 한 구현이 있습니다. http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx

중괄호 이스케이프와 관련된 다른 구현에서 발생하는 몇 가지 문제를 해결합니다. 게시물에 세부 정보가 있습니다. DataBinder.Eval도 수행하지만 여전히 매우 빠릅니다.


3
해당 기사 404에서 다운로드 할 수있는 코드입니다. 나도 정말보고 싶어.
quentin-starin

2
@qes : 코멘트에 업데이트 된 링크가 게시되었습니다 : code.haacked.com/util/NamedStringFormatSolution.zip
Der Hochstapler

3
@OliverSalzburg : 나는 한동안 모든 포맷 요구에 SmartFormat을 사용하고 있습니다. github.com/scottrippey/SmartFormat
quentin-starin

@ qes : 그것에 대해 쓰고 답하고 작동 방식을 보여줄 수 있습니까? 흥미로워
Der Hochstapler

@ qes : SmartFormat이 매우 훌륭하고 적극적으로 지원되므로 (2015) 답변으로 SmartFormat을 확실히 추가해야합니다.
Răzvan Flavius ​​Panda

42

보간 된 문자열 이 C # 6.0 및 Visual Basic 14에 추가되었습니다.

둘 다 Visual Studio 2015 에서 새로운 Roslyn 컴파일러를 통해 소개되었습니다 .

  • C # 6.0 :

    return "\{someVariable} and also \{someOtherVariable}" 또는
    return $"{someVariable} and also {someOtherVariable}"

  • VB 14 :

    return $"{someVariable} and also {someOtherVariable}"

주목할만한 기능 (Visual Studio 2015 IDE) :

  • 구문 색상 이 지원됩니다-문자열에 포함 된 변수가 강조 표시됩니다
  • 리팩토링 지원-이름을 바꾸면 문자열에 포함 된 변수의 이름도 바뀝니다.
  • 실제로 변수 이름뿐만 아니라 표현식 도 지원됩니다. 예 : {index}작동 할뿐만 아니라{(index + 1).ToString().Trim()}

즐겨! (VS에서 "웃음 보내기"를 클릭하십시오)


2
질문에 .net 3.5 태그가 붙어 있으므로 귀하의 정보는 유효하지만 대안은 아닙니다
Douglas Gandini

1
@miroxlav-프레임 워크 버전이 맞습니다. 문자열 보간는 VS 2015에서 사용되는 새로운 로슬린 컴파일러에 따라 달라집니다
더글러스 Gandini에

2
형식 문자열을 코드 자체에 넣지 않으면 작동하지 않습니다. 즉, 형식 문자열이 구성 파일이나 데이터베이스와 같은 외부 소스에서 온 경우 작동하지 않습니다.
Craig Brett

40

다음과 같이 익명 유형을 사용할 수도 있습니다.

    public string Format(string input, object p)
    {
        foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(p))
            input = input.Replace("{" + prop.Name + "}", (prop.GetValue(p) ?? "(null)").ToString());

        return input;
    }

물론 형식화를 구문 분석하려면 더 많은 코드가 필요하지만 다음과 같이이 함수를 사용하여 문자열의 형식을 지정할 수 있습니다.

Format("test {first} and {another}", new { first = "something", another = "something else" })

1
여전히 2.0 인 우리에게 완벽합니다. 네, 알아요 ...이 솔루션은 간단하고 이해하기 쉽습니다. 그리고 그것은 작동합니다 !!!
Brad Bruce

14

이 작업을 즉시 수행 할 수있는 방법이없는 것 같습니다. 그러나 for 값에 IFormatProvider연결되는 자체 구현을 구현하는 것이 가능해 보입니다 IDictionary.

var Stuff = new Dictionary<string, object> {
   { "language", "Python" },
   { "#", 2 }
};
var Formatter = new DictionaryFormatProvider();

// Interpret {0:x} where {0}=IDictionary and "x" is hash key
Console.WriteLine string.Format(Formatter, "{0:language} has {0:#} quote types", Stuff);

출력 :

파이썬에는 2 개의 따옴표 유형이 있습니다

주의 할 점은 혼합 할 수 없으므로 FormatProviders멋진 텍스트 형식을 동시에 사용할 수 없다는 것입니다.


1
mo.notono.us/2008/07/c-stringinject-format-strings-by-key.html 에서 멋진 구현을 제공하는 최고의 개념 방법 인 IMHO를 설명하기 위해 + 1- 다른 게시물에는 다음이 포함되지만 반사 기반의 방법을 제안하는 IMHO, 오히려 악
아담 랄프

9

프레임 워크 자체는이를 수행하는 방법을 제공하지 않지만 Scott Hanselman 의이 게시물 을 살펴볼 수 있습니다 . 사용법 예 :

Person p = new Person();  
string foo = p.ToString("{Money:C} {LastName}, {ScottName} {BirthDate}");  
Assert.AreEqual("$3.43 Hanselman, {ScottName} 1/22/1974 12:00:00 AM", foo); 

James Newton-King 의이 코드 는 유사하며 하위 속성 및 색인과 작동합니다.

string foo = "Top result for {Name} was {Results[0].Name}".FormatWith(student));

James의 코드는 System.Web.UI.DataBinder에 의존합니다. 문자열을 구문 분석하고 어떤 사람들은 비 - 웹 애플리케이션에서 수행하는 좋아하지 않아 System.Web을 참조 할 필요합니다.

편집 : 아, 속성이 준비된 객체가 없다면 익명 유형과 잘 작동합니다.

string name = ...;
DateTime date = ...;
string foo = "{Name} - {Birthday}".FormatWith(new { Name = name, Birthday = date });


4

가장 가까운 곳은 색인 형식이라고 생각합니다.

String.Format("{0} has {1} quote types.", "C#", "1");

String.Replace ()도 있습니다. 여러 단계로 수행하고 문자열의 다른 곳에서 '변수'를 찾을 수 없다는 믿음을 가지려면 다음을 수행하십시오.

string MyString = "{language} has {n} quote types.";
MyString = MyString.Replace("{language}", "C#").Replace("{n}", "1");

이것을 확장하여 목록을 사용하십시오.

List<KeyValuePair<string, string>> replacements = GetFormatDictionary();  
foreach (KeyValuePair<string, string> item in replacements)
{
    MyString = MyString.Replace(item.Key, item.Value);
}

Dictionary <string, string>에서도 .Keys 컬렉션을 반복하여 그렇게 할 수 있지만 List <KeyValuePair <string, string >>을 사용하면 List의 .ForEach () 메서드를 활용하여 다시 압축 할 수 있습니다 원 라이너 :

replacements.ForEach(delegate(KeyValuePair<string,string>) item) { MyString = MyString.Replace(item.Key, item.Value);});

람다는 훨씬 간단하지만 여전히 .Net 2.0을 사용하고 있습니다. 또한 .Net의 문자열은 변경할 수 없으므로 .Replace () 성능이 반복적으로 사용될 때 별빛이 아닙니다. 또한 MyString변수를 대리자가 액세스 할 수있는 방식으로 정의 해야 하므로 아직 완벽하지는 않습니다.


글쎄, 그게 가장 예쁜 해결책은 아니지만 지금은 내가 할 일입니다. 내가 다르게 한 것은 문자열 대신 StringBuilder를 사용하여 새 문자열을 계속 만들지 않는 것입니다.
Jason Baker

3

내 오픈 소스 라이브러리 인 Regextra 는 이름 지정된 형식을 지원합니다 (다른 것들 중에서). 현재 .NET 4.0 이상을 대상으로하며 NuGet에서 사용할 수 있습니다 . 또한 Regextra : (문제)를 줄이는 데 도움 이되는 소개 블로그 게시물이 있습니다. {2} .

명명 된 형식 비트는 다음을 지원합니다.

  • 기본 형식
  • 중첩 속성 형식
  • 사전 형식
  • 분리 문자 이스케이프
  • 표준 / 사용자 정의 / IFormatProvider 문자열 형식

예:

var order = new
{
    Description = "Widget",
    OrderDate = DateTime.Now,
    Details = new
    {
        UnitPrice = 1500
    }
};

string template = "We just shipped your order of '{Description}', placed on {OrderDate:d}. Your {{credit}} card will be billed {Details.UnitPrice:C}.";

string result = Template.Format(template, order);
// or use the extension: template.FormatTemplate(order);

결과:

2014 년 2 월 28 일에 '위젯'주문을 배송했습니다. 귀하의 {credit} 카드에 $ 1,500.00이 청구됩니다.

다른 예제는 프로젝트의 GitHub 링크 (위)와 위키를 확인하십시오.


와우, 이것은 특히 어려운 형식 예제 중 일부를 다루는 경우에 놀랍습니다.
Nicholas Petersen

2

이것을 확인하십시오 :

public static string StringFormat(string format, object source)
{
    var matches = Regex.Matches(format, @"\{(.+?)\}");
    List<string> keys = (from Match matche in matches select matche.Groups[1].Value).ToList();

    return keys.Aggregate(
        format,
        (current, key) =>
        {
            int colonIndex = key.IndexOf(':');
            return current.Replace(
                "{" + key + "}",
                colonIndex > 0
                    ? DataBinder.Eval(source, key.Substring(0, colonIndex), "{0:" + key.Substring(colonIndex + 1) + "}")
                    : DataBinder.Eval(source, key).ToString());
        });
}

견본:

string format = "{foo} is a {bar} is a {baz} is a {qux:#.#} is a really big {fizzle}";
var o = new { foo = 123, bar = true, baz = "this is a test", qux = 123.45, fizzle = DateTime.Now };
Console.WriteLine(StringFormat(format, o));

다른 솔루션에 비해 성능이 꽤 좋습니다.


1

이것이 가능할지는 의문입니다. 가장 먼저 염두에 두어야 할 것은 지역 변수 이름에 어떻게 접근 할 것인가입니다.

그러나이를 위해 LINQ 및 Lambda 표현식을 사용하는 영리한 방법이있을 수 있습니다.


@leppie : +1 LINQ + Lambda를 제공 할 수있는 경우 +1; D (관련 답변이 있으면 +1)
user7116

나도보고 싶어요! 어쩌면 나는 그 도전을 할 것입니다!
leppie

나는 변수 이름으로 할 수 없을 것이라고 생각했지만, 내가 틀렸다면 그것을 넣습니다. :) 사전으로 이것을 할 수있는 방법이 없습니까?
Jason Baker

나는 어딘가에 시도했지만 그것을 너무 못 생겼고 사용하기 어렵다고 생각했다. 다음과 같이 보일 것입니다 : string s = format (f => f ( "{hello} {world}", hello, world));
leppie

1

여기 내가 얼마 전에 만들었습니다. 단일 인수를 사용하여 Format 메소드를 사용하여 String을 확장합니다. 좋은 점은 표준 문자열을 사용한다는 것입니다 .int와 같은 간단한 인수를 제공하면 익명 형식과 같은 것을 사용하면 작동합니다.

사용법 예 :

"The {Name} family has {Children} children".Format(new { Children = 4, Name = "Smith" })

"스미스 가족은 자녀가 4 명입니다."

배열 및 인덱서와 같은 미친 바인딩 작업을 수행하지 않습니다. 그러나 매우 간단하고 고성능입니다.

    public static class AdvancedFormatString
{

    /// <summary>
    /// An advanced version of string.Format.  If you pass a primitive object (string, int, etc), it acts like the regular string.Format.  If you pass an anonmymous type, you can name the paramters by property name.
    /// </summary>
    /// <param name="formatString"></param>
    /// <param name="arg"></param>
    /// <returns></returns>
    /// <example>
    /// "The {Name} family has {Children} children".Format(new { Children = 4, Name = "Smith" })
    /// 
    /// results in 
    /// "This Smith family has 4 children
    /// </example>
    public static string Format(this string formatString, object arg, IFormatProvider format = null)
    {
        if (arg == null)
            return formatString;

        var type = arg.GetType();
        if (Type.GetTypeCode(type) != TypeCode.Object || type.IsPrimitive)
            return string.Format(format, formatString, arg);

        var properties = TypeDescriptor.GetProperties(arg);
        return formatString.Format((property) =>
            {
                var value = properties[property].GetValue(arg);
                return Convert.ToString(value, format);
            });
    }


    public static string Format(this string formatString, Func<string, string> formatFragmentHandler)
    {
        if (string.IsNullOrEmpty(formatString))
            return formatString;
        Fragment[] fragments = GetParsedFragments(formatString);
        if (fragments == null || fragments.Length == 0)
            return formatString;

        return string.Join(string.Empty, fragments.Select(fragment =>
            {
                if (fragment.Type == FragmentType.Literal)
                    return fragment.Value;
                else
                    return formatFragmentHandler(fragment.Value);
            }).ToArray());
    }


    private static Fragment[] GetParsedFragments(string formatString)
    {
        Fragment[] fragments;
        if ( parsedStrings.TryGetValue(formatString, out fragments) )
        {
            return fragments;
        }
        lock (parsedStringsLock)
        {
            if ( !parsedStrings.TryGetValue(formatString, out fragments) )
            {
                fragments = Parse(formatString);
                parsedStrings.Add(formatString, fragments);
            }
        }
        return fragments;
    }

    private static Object parsedStringsLock = new Object();
    private static Dictionary<string,Fragment[]> parsedStrings = new Dictionary<string,Fragment[]>(StringComparer.Ordinal);

    const char OpeningDelimiter = '{';
    const char ClosingDelimiter = '}';

    /// <summary>
    /// Parses the given format string into a list of fragments.
    /// </summary>
    /// <param name="format"></param>
    /// <returns></returns>
    static Fragment[] Parse(string format)
    {
        int lastCharIndex = format.Length - 1;
        int currFragEndIndex;
        Fragment currFrag = ParseFragment(format, 0, out currFragEndIndex);

        if (currFragEndIndex == lastCharIndex)
        {
            return new Fragment[] { currFrag };
        }

        List<Fragment> fragments = new List<Fragment>();
        while (true)
        {
            fragments.Add(currFrag);
            if (currFragEndIndex == lastCharIndex)
            {
                break;
            }
            currFrag = ParseFragment(format, currFragEndIndex + 1, out currFragEndIndex);
        }
        return fragments.ToArray();

    }

    /// <summary>
    /// Finds the next delimiter from the starting index.
    /// </summary>
    static Fragment ParseFragment(string format, int startIndex, out int fragmentEndIndex)
    {
        bool foundEscapedDelimiter = false;
        FragmentType type = FragmentType.Literal;

        int numChars = format.Length;
        for (int i = startIndex; i < numChars; i++)
        {
            char currChar = format[i];
            bool isOpenBrace = currChar == OpeningDelimiter;
            bool isCloseBrace = isOpenBrace ? false : currChar == ClosingDelimiter;

            if (!isOpenBrace && !isCloseBrace)
            {
                continue;
            }
            else if (i < (numChars - 1) && format[i + 1] == currChar)
            {//{{ or }}
                i++;
                foundEscapedDelimiter = true;
            }
            else if (isOpenBrace)
            {
                if (i == startIndex)
                {
                    type = FragmentType.FormatItem;
                }
                else
                {

                    if (type == FragmentType.FormatItem)
                        throw new FormatException("Two consequtive unescaped { format item openers were found.  Either close the first or escape any literals with another {.");

                    //curr character is the opening of a new format item.  so we close this literal out
                    string literal = format.Substring(startIndex, i - startIndex);
                    if (foundEscapedDelimiter)
                        literal = ReplaceEscapes(literal);

                    fragmentEndIndex = i - 1;
                    return new Fragment(FragmentType.Literal, literal);
                }
            }
            else
            {//close bracket
                if (i == startIndex || type == FragmentType.Literal)
                    throw new FormatException("A } closing brace existed without an opening { brace.");

                string formatItem = format.Substring(startIndex + 1, i - startIndex - 1);
                if (foundEscapedDelimiter)
                    formatItem = ReplaceEscapes(formatItem);//a format item with a { or } in its name is crazy but it could be done
                fragmentEndIndex = i;
                return new Fragment(FragmentType.FormatItem, formatItem);
            }
        }

        if (type == FragmentType.FormatItem)
            throw new FormatException("A format item was opened with { but was never closed.");

        fragmentEndIndex = numChars - 1;
        string literalValue = format.Substring(startIndex);
        if (foundEscapedDelimiter)
            literalValue = ReplaceEscapes(literalValue);

        return new Fragment(FragmentType.Literal, literalValue);

    }

    /// <summary>
    /// Replaces escaped brackets, turning '{{' and '}}' into '{' and '}', respectively.
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    static string ReplaceEscapes(string value)
    {
        return value.Replace("{{", "{").Replace("}}", "}");
    }

    private enum FragmentType
    {
        Literal,
        FormatItem
    }

    private class Fragment
    {

        public Fragment(FragmentType type, string value)
        {
            Type = type;
            Value = value;
        }

        public FragmentType Type
        {
            get;
            private set;
        }

        /// <summary>
        /// The literal value, or the name of the fragment, depending on fragment type.
        /// </summary>
        public string Value
        {
            get;
            private set;
        }


    }

}

1
private static Regex s_NamedFormatRegex = new Regex(@"\{(?!\{)(?<key>[\w]+)(:(?<fmt>(\{\{|\}\}|[^\{\}])*)?)?\}", RegexOptions.Compiled);

public static StringBuilder AppendNamedFormat(this StringBuilder builder,IFormatProvider provider, string format, IDictionary<string, object> args)
{
    if (builder == null) throw new ArgumentNullException("builder");
    var str = s_NamedFormatRegex.Replace(format, (mt) => {
        string key = mt.Groups["key"].Value;
        string fmt = mt.Groups["fmt"].Value;
        object value = null;
        if (args.TryGetValue(key,out value)) {
            return string.Format(provider, "{0:" + fmt + "}", value);
        } else {
            return mt.Value;
        }
    });
    builder.Append(str);
    return builder;
}

public static StringBuilder AppendNamedFormat(this StringBuilder builder, string format, IDictionary<string, object> args)
{
    if (builder == null) throw new ArgumentNullException("builder");
    return builder.AppendNamedFormat(null, format, args);
}

예:

var builder = new StringBuilder();
builder.AppendNamedFormat(
@"你好,{Name},今天是{Date:yyyy/MM/dd}, 这是你第{LoginTimes}次登录,积分{Score:{{ 0.00 }}}",
new Dictionary<string, object>() { 
    { "Name", "wayjet" },
    { "LoginTimes",18 },
    { "Score", 100.4 },
    { "Date",DateTime.Now }
});

산출 : 你好, wayjet, 今天 是 2011-05-04, 这 是 你 第 18 次 登录, 积分 {100.40}


1

다음은 모든 객체에 대한 간단한 방법입니다.

    using System.Text.RegularExpressions;
    using System.ComponentModel;

    public static string StringWithFormat(string format, object args)
    {
        Regex r = new Regex(@"\{([A-Za-z0-9_]+)\}");

        MatchCollection m = r.Matches(format);

        var properties = TypeDescriptor.GetProperties(args);

        foreach (Match item in m)
        {
            try
            {
                string propertyName = item.Groups[1].Value;
                format = format.Replace(item.Value, properties[propertyName].GetValue(args).ToString());
            }
            catch
            {
                throw new FormatException("The format string is not valid");
            }
        }

        return format;
    }

그리고 그것을 사용하는 방법 :

 DateTime date = DateTime.Now;
 string dateString = StringWithFormat("{Month}/{Day}/{Year}", date);

출력 : 2012 년 2 월 27 일


0

나는 이것이 클래스를 사용할 때를 제외하고 String.Format의 기능을 복제하는 간단한 클래스입니다. 사전 또는 유형을 사용하여 필드를 정의 할 수 있습니다.

https://github.com/SergueiFedorov/NamedFormatString

C # 6.0은이 기능을 언어 사양에 바로 추가하므로 NamedFormatString이전 버전과의 호환성을위한 것입니다.


0

기존 솔루션과 약간 다른 방식으로이 문제를 해결했습니다. 이름이 지정된 항목 대체의 핵심을 수행합니다 (일부는 수행 한 리플렉션 비트가 아님). 매우 빠르고 간단합니다 ... 이것은 내 해결책입니다.

/// <summary>
/// Formats a string with named format items given a template dictionary of the items values to use.
/// </summary>
public class StringTemplateFormatter
{
    private readonly IFormatProvider _formatProvider;

    /// <summary>
    /// Constructs the formatter with the specified <see cref="IFormatProvider"/>.
    /// This is defaulted to <see cref="CultureInfo.CurrentCulture">CultureInfo.CurrentCulture</see> if none is provided.
    /// </summary>
    /// <param name="formatProvider"></param>
    public StringTemplateFormatter(IFormatProvider formatProvider = null)
    {
        _formatProvider = formatProvider ?? CultureInfo.CurrentCulture;
    }

    /// <summary>
    /// Formats a string with named format items given a template dictionary of the items values to use.
    /// </summary>
    /// <param name="text">The text template</param>
    /// <param name="templateValues">The named values to use as replacements in the formatted string.</param>
    /// <returns>The resultant text string with the template values replaced.</returns>
    public string FormatTemplate(string text, Dictionary<string, object> templateValues)
    {
        var formattableString = text;
        var values = new List<object>();
        foreach (KeyValuePair<string, object> value in templateValues)
        {
            var index = values.Count;
            formattableString = ReplaceFormattableItem(formattableString, value.Key, index);
            values.Add(value.Value);
        }
        return String.Format(_formatProvider, formattableString, values.ToArray());
    }

    /// <summary>
    /// Convert named string template item to numbered string template item that can be accepted by <see cref="string.Format(string,object[])">String.Format</see>
    /// </summary>
    /// <param name="formattableString">The string containing the named format item</param>
    /// <param name="itemName">The name of the format item</param>
    /// <param name="index">The index to use for the item value</param>
    /// <returns>The formattable string with the named item substituted with the numbered format item.</returns>
    private static string ReplaceFormattableItem(string formattableString, string itemName, int index)
    {
        return formattableString
            .Replace("{" + itemName + "}", "{" + index + "}")
            .Replace("{" + itemName + ",", "{" + index + ",")
            .Replace("{" + itemName + ":", "{" + index + ":");
    }
}

다음과 같은 방식으로 사용됩니다.

    [Test]
    public void FormatTemplate_GivenANamedGuid_FormattedWithB_ShouldFormatCorrectly()
    {
        // Arrange
        var template = "My guid {MyGuid:B} is awesome!";
        var templateValues = new Dictionary<string, object> { { "MyGuid", new Guid("{A4D2A7F1-421C-4A1D-9CB2-9C2E70B05E19}") } };
        var sut = new StringTemplateFormatter();
        // Act
        var result = sut.FormatTemplate(template, templateValues);
        //Assert
        Assert.That(result, Is.EqualTo("My guid {a4d2a7f1-421c-4a1d-9cb2-9c2e70b05e19} is awesome!"));
    }

누군가가 이것을 유용하게 사용하기를 바랍니다!


0

허용 된 답변이 좋은 예를 제시하지만 .Inject 및 일부 Haack 예는 탈출을 처리하지 않습니다. 많은 사람들은 또한 .NET Core 및 일부 다른 환경에서는 사용할 수없는 Regex (느린) 또는 DataBinder.Eval에 크게 의존합니다.

이를 염두에두고, 문자를 통해 스트리밍하고 StringBuilder출력에 문자별로 스트리밍하는 간단한 상태 머신 기반 파서를 작성했습니다 . String확장 방법 으로 구현되며 a Dictionary<string, object>또는object 입력으로 (반사 사용) 매개 변수를 사용 매개 변수를 .

입력에 불균형 괄호 및 / 또는 기타 오류가 포함되어 있으면 무제한 레벨을 처리 {{{escaping}}}하고 던집니다 FormatException.

public static class StringExtension {
    /// <summary>
    /// Extension method that replaces keys in a string with the values of matching object properties.
    /// </summary>
    /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
    /// <param name="injectionObject">The object whose properties should be injected in the string</param>
    /// <returns>A version of the formatString string with keys replaced by (formatted) key values.</returns>
    public static string FormatWith(this string formatString, object injectionObject) {
        return formatString.FormatWith(GetPropertiesDictionary(injectionObject));
    }

    /// <summary>
    /// Extension method that replaces keys in a string with the values of matching dictionary entries.
    /// </summary>
    /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
    /// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
    /// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
    public static string FormatWith(this string formatString, IDictionary<string, object> dictionary) {
        char openBraceChar = '{';
        char closeBraceChar = '}';

        return FormatWith(formatString, dictionary, openBraceChar, closeBraceChar);
    }
        /// <summary>
        /// Extension method that replaces keys in a string with the values of matching dictionary entries.
        /// </summary>
        /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
        /// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
        /// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
    public static string FormatWith(this string formatString, IDictionary<string, object> dictionary, char openBraceChar, char closeBraceChar) {
        string result = formatString;
        if (dictionary == null || formatString == null)
            return result;

        // start the state machine!

        // ballpark output string as two times the length of the input string for performance (avoids reallocating the buffer as often).
        StringBuilder outputString = new StringBuilder(formatString.Length * 2);
        StringBuilder currentKey = new StringBuilder();

        bool insideBraces = false;

        int index = 0;
        while (index < formatString.Length) {
            if (!insideBraces) {
                // currently not inside a pair of braces in the format string
                if (formatString[index] == openBraceChar) {
                    // check if the brace is escaped
                    if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
                        // add a brace to the output string
                        outputString.Append(openBraceChar);
                        // skip over braces
                        index += 2;
                        continue;
                    }
                    else {
                        // not an escaped brace, set state to inside brace
                        insideBraces = true;
                        index++;
                        continue;
                    }
                }
                else if (formatString[index] == closeBraceChar) {
                    // handle case where closing brace is encountered outside braces
                    if (index < formatString.Length - 1 && formatString[index + 1] == closeBraceChar) {
                        // this is an escaped closing brace, this is okay
                        // add a closing brace to the output string
                        outputString.Append(closeBraceChar);
                        // skip over braces
                        index += 2;
                        continue;
                    }
                    else {
                        // this is an unescaped closing brace outside of braces.
                        // throw a format exception
                        throw new FormatException($"Unmatched closing brace at position {index}");
                    }
                }
                else {
                    // the character has no special meaning, add it to the output string
                    outputString.Append(formatString[index]);
                    // move onto next character
                    index++;
                    continue;
                }
            }
            else {
                // currently inside a pair of braces in the format string
                // found an opening brace
                if (formatString[index] == openBraceChar) {
                    // check if the brace is escaped
                    if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
                        // there are escaped braces within the key
                        // this is illegal, throw a format exception
                        throw new FormatException($"Illegal escaped opening braces within a parameter - index: {index}");
                    }
                    else {
                        // not an escaped brace, we have an unexpected opening brace within a pair of braces
                        throw new FormatException($"Unexpected opening brace inside a parameter - index: {index}");
                    }
                }
                else if (formatString[index] == closeBraceChar) {
                    // handle case where closing brace is encountered inside braces
                    // don't attempt to check for escaped braces here - always assume the first brace closes the braces
                    // since we cannot have escaped braces within parameters.

                    // set the state to be outside of any braces
                    insideBraces = false;

                    // jump over brace
                    index++;

                    // at this stage, a key is stored in current key that represents the text between the two braces
                    // do a lookup on this key
                    string key = currentKey.ToString();
                    // clear the stringbuilder for the key
                    currentKey.Clear();

                    object outObject;

                    if (!dictionary.TryGetValue(key, out outObject)) {
                        // the key was not found as a possible replacement, throw exception
                        throw new FormatException($"The parameter \"{key}\" was not present in the lookup dictionary");
                    }

                    // we now have the replacement value, add the value to the output string
                    outputString.Append(outObject);

                    // jump to next state
                    continue;
                } // if }
                else {
                    // character has no special meaning, add it to the current key
                    currentKey.Append(formatString[index]);
                    // move onto next character
                    index++;
                    continue;
                } // else
            } // if inside brace
        } // while

        // after the loop, if all braces were balanced, we should be outside all braces
        // if we're not, the input string was misformatted.
        if (insideBraces) {
            throw new FormatException("The format string ended before the parameter was closed.");
        }

        return outputString.ToString();
    }

    /// <summary>
    /// Creates a Dictionary from an objects properties, with the Key being the property's
    /// name and the Value being the properties value (of type object)
    /// </summary>
    /// <param name="properties">An object who's properties will be used</param>
    /// <returns>A <see cref="Dictionary"/> of property values </returns>
    private static Dictionary<string, object> GetPropertiesDictionary(object properties) {
        Dictionary<string, object> values = null;
        if (properties != null) {
            values = new Dictionary<string, object>();
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(properties);
            foreach (PropertyDescriptor prop in props) {
                values.Add(prop.Name, prop.GetValue(properties));
            }
        }
        return values;
    }
}

궁극적으로 모든 논리는 10 가지 주요 상태로 요약됩니다. 상태 머신이 브래킷 외부에 있고 마찬가지로 브래킷 내부에있을 때 다음 문자는 열린 브레이스, 이스케이프 된 열린 브레이스, 닫힌 브레이스, 이스케이프 된 닫힌 브레이스, 또는 평범한 성격. 이러한 각 조건은 루프가 진행됨에 따라 개별적으로 처리되어 출력 StringBuffer또는 키에 문자를 추가합니다 StringBuffer. 매개 변수가 닫히면 키 StringBuffer값이 사전에서 매개 변수의 값을 찾는 데 사용되며 출력값으로 푸시됩니다 StringBuffer. 마지막에 출력 값 StringBuffer이 반환됩니다.


-6
string language = "Python";
int numquotes = 2;
string output = language + " has "+ numquotes + " language types.";

편집 : 내가 말한 것은 "아니요, 당신이하고 싶은 것은 C #에 의해 지원된다고 생각하지 않습니다. 이것은 당신이 얻을 수있는 한 가깝습니다."


1
다운 투표가 궁금합니다. 왜 나에게 이유를 말하고 싶어?
Kevin

1
따라서 string.format 은이 작업을 4 / 10 초 더 빠르게 수행합니다.이 함수가 톤이라고하면 그 차이를 알 수 있습니다. 그러나 그것은 적어도 그가하고 싶지 않다고 이미 말했던 것과 같은 방식으로 그에게 말하지 않고 그의 질문에 적어도 대답합니다.
Kevin

4
나는 당신에게 투표하지 않았지만, 주로 문자열 연결을 많이 못하기 때문에 이것을 구현하지 않을 것입니다. 그러나 그것은 나의 개인적인 견해입니다.
Jason Baker

이 투표가 너무 많이 중단되었다는 것이 이상합니다. 연결을 자주 호출하지 않으면 "someString" + someVariable + "someOtherString"더 읽기 쉬운 것으로 간주 할 수있는 답변을 확장 해보십시오 . 이 기사 는 당신에게 동의합니다.
Steven Jeuris
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.