NET: C'è una forma String.Format per inserire il valore della proprietà di un oggetto in una stringa?

StackOverflow https://stackoverflow.com/questions/357447

  •  21-08-2019
  •  | 
  •  

Domanda

Credo che la risposta diretta alla domanda è 'No', ma spero che qualcuno ha scritto un vero e proprio semplice libreria per fare questo (o posso farlo ... ugh ...)

Lasciatemi mostrare quello che sto cercando con un esempio. Supponiamo che ho avuto il seguente:

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

Mi piacerebbe essere in grado di fare qualcosa di simile:

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

Qualcuno sa se c'è un formato per fare qualcosa di simile, o se qualcuno ha scritto una libreria per farlo? So che non dovrebbe essere troppo difficile, ma io piuttosto non sarei reimplementare la ruota.

È stato utile?

Soluzione

Edit: Non è necessario implementare IFormattable per ogni oggetto ... che saresti una valle di lacrime, limitando gravemente, e un gran lavoro di manutenzione. Basta usare Riflessione e un IFormatProvider con ICustomFormatter e si metterà a lavorare con qualsiasi oggetto. String.Format ha un sovraccarico di prendere uno come parametro.

Non ho mai pensato a questo prima, ma tu mi hai incuriosito - così ho dovuto dare un rapido vortice. Si noti che ho scelto di consentire una stringa di formato supplementare da passare al valore della proprietà, e che funziona solo con le proprietà non indicizzate e accessibili (anche se si potrebbe facilmente aggiungere che).

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

E il vostro esempio leggermente modificata:

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

Uscite:

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.

Altri suggerimenti

Si potrebbe ignorare il ToString () per la classe.

qui

Ciò che è dopo il ":" è passato come argomento al metodo ToString della classe
. Basta dichiarare un metodo ToString accettare una stringa, e il 'Nome', ecc '' NumberOfCats verrà passato in quel parametro.

EDIT: È necessario implementare System.IFormattable. Questo funziona:

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

Guarda il mio libreria "espansivo"

In Nuget qui: http://nuget.org/List/Packages/Expansive

su github qui: http://github.com/anderly/expansive

Io davvero non vedo come questo:

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

è meglio di questo:

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

In realtà, dal momento che si sta perdendo intellisense aiuto nella prima uno, non solo è più incline al fallimento, ma probabilmente necessario più tempo per scrivere in un IDE.

E se si voleva fare qualcosa di simile, si può sempre improvvisare un metodo di estensione per esso. Scommetto che sarebbe simile a un incubo, tho.

Boo o Nemerle ha qualcosa di simile. Ho cercato di pensare a un modo semplice bello farlo per un paio d'anni a questa parte, con una risposta facile.

Il meglio che puoi fare è di fornire proprietario IFormatProvider, e analizzare l'ingresso manualmente (la parte scadente ...).

Se si decide di analizzare la stringa di formato da soli, si dovrebbe prendere in considerazione questo ...:

Il progetto Spring.NET ha il Spring.NET Expression Language in Spring.Core. E ti permette di interrogare un oggetto grafico puntando proprietà utilizzando le stringhe. Usando il tuo esempio, si potrebbe immaginare qualcosa di simile:

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

(ma probabilmente non memorizzare una e-mail del genere, ma non riuscivo a trovare qualcosa di meglio)

Questo è qualcosa che molti lo fanno nel mondo Python utilizzando "% locals someString ()." Quello che stai suggerendo anche se ha un paio di pause fondamentali da opere come String.Format:

  • normalmente la notazione segnaposto ha stringa di informazioni di formattazione dopo i due punti, mentre si vuole fare l'accesso alle proprietà.

  • indici segnaposto ({0, {1, etc) normalmente si riferiscono agli argomenti numberes nelle params args, ma sembra che si desidera la funzione di rendere l'intera stringa per ogni parametro che viene passato in. dovrebbero essere concatenati? restituito come un array di stringhe?

Quindi, si può finire per scrivere questo uno voi stessi, nel qual caso si può saltare la notazione indice interamente (quindi {NumberOfCats} invece di {0: NumberOfCats} o anche utilizzare nome di proprietà seguito da provider di formato {NumberOfCats: c}. Consumare i metadati da un oggetto di ingresso non dovrebbe essere troppo difficile.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top