9

質問に対する直接的な答えは「いいえ」だと思いますが、誰かがこれを行うための本当に単純なライブラリを作成したことを望んでいます(または、私はそれを行うことができます...うーん...)

私が探しているものを例で示しましょう。次のものがあったとします。

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

私はこのようなことができるようにしたいと思います:

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);
}

このようなことを行うためのフォーマットがあるかどうか、または誰かがそれを行うためのライブラリを作成したかどうかは誰にもわかりませんか? それほど難しくないことはわかっていますが、ホイールを再実装したくありません。

4

8 に答える 8

10

編集: オブジェクトごとに IFormattable を実装する必要はありません...これは、PITA であり、制限が厳しく、メンテナンスの負担がかなり大きくなります。Reflection と IFormatProvider を ICustomFormatter で使用するだけで、どのオブジェクトでも機能します。String.Format には、1 つをパラメーターとして受け取るためのオーバーロードがあります。

これまで考えたこともなかったのですが、あなたに興味をそそられたので、ちょっと試してみました。追加のフォーマット文字列をプロパティ値に渡すことを許可することに注意してください。これは、インデックス付けされていないアクセス可能なプロパティでのみ機能します (ただし、簡単に追加できます)。

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);
    }
}

そして、わずかに変更された例:

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);

出力:

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.
于 2008-12-10T22:07:21.367 に答える
4

クラスの ToString() をオーバーライドできます。

良記事はこちら

于 2008-12-10T20:16:04.773 に答える
4

":" の後にあるものは、クラスの ToString メソッドに引数として渡されます。
文字列を受け取る ToString メソッドを宣言するだけで、'Name'、'NumberOfCats' などがそのパラメーターに渡されます。

編集: System.IFormattable を実装する必要があります。これは機能します:

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));
    }
}
于 2008-12-10T20:18:08.763 に答える
2

私のライブラリ「Expansive」をチェックしてください

Nuget の場合: http://nuget.org/List/Packages/Expansive

GitHub はこちら: http://github.com/anderly/expansive

于 2011-10-26T06:22:19.583 に答える
1

私は本当にこれがどのように見えません:

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

これよりも優れています:

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

実際、最初のバージョンでは IntelliSense のヘルプが失われているため、失敗しやすいだけでなく、IDE での記述に時間がかかる可能性があります。

このようなことをしたい場合は、いつでも拡張メソッドを作成できます。悪夢のように見えるに違いない。

于 2008-12-10T20:21:58.160 に答える
0

BooまたはNemerleにはこのようなものがあります。私はここ数年、簡単な答えがなく、それを行うための素晴らしい簡単な方法を考えようとしました。

最善の方法は、独自のIFormatProviderを提供し、入力を手動で解析することです(くだらない部分...)。

于 2008-12-10T20:25:55.453 に答える
0

フォーマット文字列を自分で解析する場合は、次のことを考慮する必要があります...:

Spring.NETプロジェクトには、 Spring.CoreにSpring.NET式言語があります。文字列を使用してプロパティをポイントすることにより、オブジェクトグラフをクエリできます。あなたの例を使用して、あなたはこのようなものを想像することができます:

var person = new Person { Name = "joe", Email = new Email { Address = "joe@joe.com" } };

var message = string.Format("{0}'s e-mail is {1}",
    ExpressionEvaluator.GetValue(person, "Name"), 
    ExpressionEvaluator.GetValue(person, "Email.Address"));

(私はおそらくそのような電子メールを保存しないでしょうが、私はこれ以上のものを思い付くことができませんでした)

于 2008-12-10T20:28:18.883 に答える
0

これは、「someString % locals()」を使用して Python の世界で多くの人が行っていることです。ただし、あなたが提案していることには、string.format の仕組みからいくつかの基本的な中断があります。

  • 通常、プレースホルダー表記にはコロンの後に文字列の書式設定情報がありますが、プロパティへのアクセスを行う必要があります。

  • プレースホルダー インデックス ({0、{1 など) は通常、params 引数の numberes 引数を参照しますが、渡された各パラメーターの文字列全体を関数にレンダリングさせたいようです。それらを連結する必要がありますか? 文字列配列として返されますか?

そのため、これを自分で作成することになる可能性があります。その場合、インデックス表記を完全にスキップできます (したがって、{0:NumberOfCats} の代わりに {NumberOfCats} を使用するか、プロパティ名の後にフォーマット プロバイダー {NumberOfCats:c} を使用することもできます)。メタデータの消費入力オブジェクトからの操作はそれほど難しくありません。

于 2008-12-10T20:22:12.733 に答える