Verbesserte interpolierte Zeichenfolgen
Anmerkung
Dieser Artikel ist eine Featurespezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.
Es kann einige Abweichungen zwischen der Featurespezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede werden in den entsprechenden Hinweisen zum Language Design Meeting (LDM) erfasst.
Weitere Informationen zum Einführen von Featurespezifikationen in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.
Champion-Problem: https://github.com/dotnet/csharplang/issues/4487
Zusammenfassung
Wir stellen ein neues Muster zur Erstellung und Nutzung von interpolierten Zeichenfolgenausdrücken vor, um eine effiziente Formatierung zu ermöglichen. Es kann sowohl in allgemeinen string
-Szenarien als auch in spezialisierteren Szenarien wie Protokollierungsframeworks eingesetzt werden, ohne unnötige Zuordnungen durch die Formatierung der Zeichenfolge im Framework zu verursachen.
Motivation
Heute besteht die Zeichenfolgeninterpolation hauptsächlich aus einem Aufruf von string.Format
. Dies kann, obwohl es allgemein verwendet wird, aus verschiedenen Gründen ineffizient sein:
- Dabei werden alle Strukturargumente verpackt, sofern die Laufzeit nicht zufällig eine Überladung von
string.Format
eingeführt hat, die genau die richtigen Typen von Argumenten in genau der richtigen Reihenfolge akzeptiert.- Diese Sortierung ist der Grund, warum die Laufzeit zögerlich ist, generische Versionen der Methode einzuführen, da sie zu einer kombinatorischen Explosion generischer Instanziierungen einer sehr gängigen Methode führen würde.
- In den meisten Fällen muss es ein Array für die Argumente allokieren.
- Es gibt keine Möglichkeit, die Instanz zu instanziieren, wenn sie nicht erforderlich ist. Protokollierungsframeworks werden beispielsweise empfehlen, die String-Interpolation zu vermeiden, da dadurch eine Zeichenfolge generiert wird, die je nach der aktuellen Protokollebene der Anwendung möglicherweise nicht benötigt wird.
- Es kann heute niemals
Span
oder andere Verweisstrukturtypen verwenden, da Refstrukturen nicht als generische Typparameter zulässig sind. Das bedeutet, wenn ein Benutzer das Kopieren an Zwischenablagen vermeiden möchte, muss er Zeichenfolgen manuell formatieren.
Intern hat die Laufzeit einen Typ namens ValueStringBuilder
, um die ersten 2 dieser Szenarien zu bewältigen. Sie übergeben einen mithilfe von „stackalloc“ erstellten Puffer an den Builder, rufen wiederholt AppendFormat
mit jedem Teil auf und erhalten schließlich eine endgültige Zeichenfolge. Wenn die resultierende Zeichenfolge die Grenzen des Stapelpuffers überschreitet, dann können sie auf ein Array im Heap zugreifen. Es ist jedoch gefährlich, diesen Typ direkt zugänglich zu machen, denn eine falsche Nutzung könnte dazu führen, dass ein gemietetes Array doppelt verworfen wird. Das kann dann zu unterschiedlichsten undefinierten Verhaltensweisen im Programm führen, weil zwei Speicherorte glauben, dass sie alleinigen Zugriff auf das gemietete Array haben. Dieser Vorschlag bietet die Möglichkeit, diesen Typ sicher aus systemeigenem C#-Code zu verwenden, indem nur ein interpoliertes Zeichenfolgenliteral geschrieben wird. Der geschriebene Code bleibt dabei unverändert und gleichzeitig wird jede interpolierte Zeichenfolge verbessert, die ein Benutzer schreibt. Außerdem wird dieses Muster erweitert, um interpolierte Zeichenfolgen zuzulassen, die als Argumente an andere Methoden übergeben werden, wobei ein Handlermuster verwendet wird, das durch den Empfänger der Methode definiert wird. Dadurch können z. B. Protokollierungsframeworks vermeiden, Zeichenfolgen zuzuordnen, die niemals benötigt werden, und C#-Benutzern eine vertraute und bequeme Interpolationssyntax bieten.
Detailentwurf
Das Handlermuster
Wir stellen ein neues Handlermuster vor, das eine interpolierte Zeichenfolge darstellen kann, die als Argument an eine Methode übergeben wird. Das einfache Englisch des Musters lautet wie folgt:
Wenn ein interpolated_string_expression als Argument an eine Methode übergeben wird, betrachten wir den Typ des Parameters. Wenn der Parametertyp über einen Konstruktor verfügt, der mit 2 int-Parametern literalLength
und formattedCount
aufgerufen werden kann, optional zusätzliche Parameter annimmt, die durch ein Attribut des ursprünglichen Parameters angegeben werden, optional über einen ausgehenden booleschen nachgestellten Parameter verfügt, und der Typ des ursprünglichen Parameters die Instanzmethoden AppendLiteral
und AppendFormatted
besitzt, die für jeden Teil der interpolierten Zeichenfolge aufgerufen werden können, dann nutzen wir diese Möglichkeiten zur Interpolation anstatt eines herkömmlichen Aufrufs von string.Format(formatStr, args)
. Ein konkretes Beispiel ist hilfreich, um sich dies vorzustellen.
// The handler that will actually "build" the interpolated string"
[InterpolatedStringHandler]
public ref struct TraceLoggerParamsInterpolatedStringHandler
{
// Storage for the built-up string
private bool _logLevelEnabled;
public TraceLoggerParamsInterpolatedStringHandler(int literalLength, int formattedCount, Logger logger, out bool handlerIsValid)
{
if (!logger._logLevelEnabled)
{
handlerIsValid = false;
return;
}
handlerIsValid = true;
_logLevelEnabled = logger.EnabledLevel;
}
public void AppendLiteral(string s)
{
// Store and format part as required
}
public void AppendFormatted<T>(T t)
{
// Store and format part as required
}
}
// The logger class. The user has an instance of this, accesses it via static state, or some other access
// mechanism
public class Logger
{
// Initialization code omitted
public LogLevel EnabledLevel;
public void LogTrace([InterpolatedStringHandlerArguments("")]TraceLoggerParamsInterpolatedStringHandler handler)
{
// Impl of logging
}
}
Logger logger = GetLogger(LogLevel.Info);
// Given the above definitions, usage looks like this:
var name = "Fred Silberberg";
logger.LogTrace($"{name} will never be printed because info is < trace!");
// This is converted to:
var name = "Fred Silberberg";
var receiverTemp = logger;
var handler = new TraceLoggerParamsInterpolatedStringHandler(literalLength: 47, formattedCount: 1, receiverTemp, out var handlerIsValid);
if (handlerIsValid)
{
handler.AppendFormatted(name);
handler.AppendLiteral(" will never be printed because info is < trace!");
}
receiverTemp.LogTrace(handler);
Da TraceLoggerParamsInterpolatedStringHandler
einen Konstruktor mit den richtigen Parametern aufweist, sagen wir, dass die interpolierte Zeichenfolge eine implizite Handlerkonvertierung in diesen Parameter aufweist und auf das oben gezeigte Muster reduziert wird. Die für dies erforderliche Spezifikation ist ein bisschen kompliziert und wird weiter unten erweitert.
Der Rest dieses Vorschlags wird Append...
verwenden, um auf eine der AppendLiteral
oder AppendFormatted
in Fällen zu verweisen, in denen beide anwendbar sind.
Neue Attribute
Der Compiler erkennt die System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute
:
using System;
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedStringHandlerAttribute : Attribute
{
public InterpolatedStringHandlerAttribute()
{
}
}
}
Dieses Attribut wird vom Compiler verwendet, um zu bestimmen, ob ein Typ ein gültiger interpolierter Zeichenfolgenhandlertyp ist.
Der Compiler erkennt auch die System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute
:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedStringHandlerArgumentAttribute : Attribute
{
public InterpolatedHandlerArgumentAttribute(string argument);
public InterpolatedHandlerArgumentAttribute(params string[] arguments);
public string[] Arguments { get; }
}
}
Dieses Attribut wird für Parameter verwendet, um den Compiler darüber zu informieren, wie sie ein Muster von Handlern für interpolierte Zeichenfolgen verringern, das in einer Parameterposition verwendet wird.
Handlerkonvertierung einer interpolierten Zeichenfolge
Der Typ T
wird als applicable_interpolated_string_handler_type bezeichnet, wenn er mit System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute
versehen ist.
Es gibt eine implizite interpolated_string_handler_conversion (Handlerkonvertierung einer interpolierten Zeichenfolge) zu T
, die von einem interpolated_string_expression (interpolierten Zeichenfolgenausdruck) herrührt oder aus einem additive_expression (additiven Ausdruck), der vollständig aus „_interpolated_string_expression_s“ besteht und nur +
-Operatoren verwendet.
Der Einfachheit halber bezieht sich interpolated_string_expression sowohl auf eine einfache interpolated_string_expression als auch auf einen additive_expression, der vollständig aus „_interpolated_string_expression_s“ besteht und nur +
-Operatoren verwendet.
Beachten Sie, dass diese Konvertierung immer vorhanden ist, unabhängig davon, ob später Fehler auftreten, wenn sie tatsächlich versuchen, die Interpolation mithilfe des Handlermusters zu verringern. Dies geschieht, um sicherzustellen, dass vorhersehbare und nützliche Fehler vorhanden sind und dass sich das Laufzeitverhalten basierend auf dem Inhalt einer interpolierten Zeichenfolge nicht ändert.
Anwendbare Funktionsmitgliedsanpassungen
Wir passen den Wortlaut des anwendbaren Funktionselementalgorithmus (§12.6.4.2) wie folgt an (ein neues Unterzeichen wird jedem Abschnitt fett hinzugefügt):
Ein Funktionselement wird als anwendbares Funktionselement in Bezug auf eine Argumentliste A
bezeichnet, wenn alle folgenden Bedingungen zutreffen.
- Jedes Argument in
A
entspricht einem Parameter in der Funktionsmitglieddeklaration, wie in Entsprechende Parameter beschrieben (§12.6.2.2), und alle Parameter, denen kein Argument entspricht, sind optionale Parameter. - Für jedes Argument in
A
ist der Parameterübergabemodus des Arguments (d. h. Wert,ref
oderout
) identisch mit dem Parameterübergabemodus des entsprechenden Parameters und- für einen Wertparameter oder ein Parameterarray existiert eine implizite Konvertierung (§10.2) vom Argument zum Typ des entsprechenden Parameters oder
- für einen
ref
-Parameter, dessen Typ ein Strukturtyp ist, ist eine implizite interpolated_string_handler_conversion vom Argument zum Typ des entsprechenden Parameters vorhanden, oder - für einen
ref
- oderout
-Parameter ist der Typ des Arguments identisch mit dem Typ des entsprechenden Parameters. Schließlich ist einref
- oderout
-Parameter ein Alias für das übergebene Argument.
Für einen Funktionsmember, der ein Parameterarray enthält, gilt, dass falls er nach den oben genannten Regeln anwendbar ist, in seiner Normalform anwendbar ist. Wenn ein Funktionselement, das ein Parameterarray enthält, nicht in seiner normalen Form anwendbar ist, kann das Funktionselement stattdessen in seiner erweiterten Formanwendbar sein:
- Das erweiterte Formular wird erstellt, indem das Parameterarray in der Funktionselementdeklaration durch null oder mehr Wertparameter des Elementtyps des Parameterarrays ersetzt wird, sodass die Anzahl der Argumente in der Argumentliste
A
der Gesamtzahl der Parameter entspricht. WennA
weniger Argumente als die Anzahl der festen Parameter in der Funktionsmememmdeklaration aufweist, kann die erweiterte Form des Funktionsmemems nicht erstellt werden und ist somit nicht anwendbar. - Andernfalls gilt das erweiterte Formular, wenn für jedes Argument in
A
der Parameterübergabemodus des Arguments mit dem Parameterübergabemodus des entsprechenden Parameters identisch ist, und- für einen festen Wertparameter oder einen Wertparameter, der durch die Erweiterung erstellt wird, ist eine implizite Konvertierung (§10.2) vom Typ des Arguments bis zum Typ des entsprechenden Parameters vorhanden, oder
- für einen
ref
-Parameter, dessen Typ ein Strukturtyp ist, ist eine implizite interpolated_string_handler_conversion vom Argument zum Typ des entsprechenden Parameters vorhanden, oder - für einen
ref
- oderout
-Parameter ist der Typ des Arguments identisch mit dem Typ des entsprechenden Parameters.
Wichtiger Hinweis: Dies bedeutet, dass, wenn 2 andernfalls gleichwertige Überladungen vorhanden sind, die sich nur vom Typ der applicable_interpolated_string_handler_typeunterscheiden, diese Überladungen als mehrdeutig betrachtet werden. Da wir explizite Umwandlungen nicht sehen, ist es möglich, dass es ein nicht auflösbares Szenario geben könnte, in dem beide anwendbare Überladungen InterpolatedStringHandlerArguments
verwenden und überhaupt nicht aufgerufen werden können, ohne das Muster zur Handler-Verringerung auszuführen. Wir könnten möglicherweise Änderungen am besseren Funktionsmitgliedalgorithmus vornehmen, um dies zu beheben, wenn wir dies wollen, aber dieses Szenario ist unwahrscheinlich, und es ist keine Priorität, die angegangen werden muss.
Anpassungen für bessere Konvertierung aus Ausdruck
Wir ändern den Abschnitt „Bessere Konvertierung aus Ausdruck“ (§12.6.4.5) wie folgt:
Aufgrund einer impliziten Konvertierung C1
, die von einem Ausdruck E
in einen Typ T1
konvertiert wird, und einer impliziten Konvertierung C2
, die von einem Ausdruck E
in einen Typ T2
konvertiert wird, ist C1
eine bessere Konvertierung als C2
wenn:
-
E
ist ein nicht konstanter interpolated_string_expression,C1
ist eine implicit_string_handler_conversion (Handlerkonvertierung einer impliziten Zeichenfolge),T1
ist ein applicable_interpolated_string_handler_type (anwendbarer Handler für implizite Zeichenfolgen), undC2
ist keine implicit_string_handler_conversion, oder E
stimmt nicht genau mitT2
überein und mindestens eine der folgenden Bedingungen trifft zu:
Dies bedeutet, dass es einige potenziell nicht offensichtliche Überladungsauflösungsregeln gibt, je nachdem, ob es sich bei der betreffenden interpolierten Zeichenfolge um einen konstanten Ausdruck handelt oder nicht. Zum Beispiel:
void Log(string s) { ... }
void Log(TraceLoggerParamsInterpolatedStringHandler p) { ... }
Log($""); // Calls Log(string s), because $"" is a constant expression
Log($"{"test"}"); // Calls Log(string s), because $"{"test"}" is a constant expression
Log($"{1}"); // Calls Log(TraceLoggerParamsInterpolatedStringHandler p), because $"{1}" is not a constant expression
Dies wird eingeführt, sodass Dinge, die einfach als Konstanten ausgeführt werden können, dies tun und keinen Aufwand verursachen, während Dinge, die nicht konstant sein können, das Handlermuster verwenden.
InterpolatedStringHandler und Verwendung
Wir führen einen neuen Typ in System.Runtime.CompilerServices
ein: DefaultInterpolatedStringHandler
. Dies ist eine Referenzstruktur mit vielen der gleichen Semantik wie ValueStringBuilder
, die für die direkte Verwendung durch den C#-Compiler vorgesehen ist. Diese Struktur würde ungefähr wie folgt aussehen:
// API Proposal issue: https://github.com/dotnet/runtime/issues/50601
namespace System.Runtime.CompilerServices
{
[InterpolatedStringHandler]
public ref struct DefaultInterpolatedStringHandler
{
public DefaultInterpolatedStringHandler(int literalLength, int formattedCount);
public string ToStringAndClear();
public void AppendLiteral(string value);
public void AppendFormatted<T>(T value);
public void AppendFormatted<T>(T value, string? format);
public void AppendFormatted<T>(T value, int alignment);
public void AppendFormatted<T>(T value, int alignment, string? format);
public void AppendFormatted(ReadOnlySpan<char> value);
public void AppendFormatted(ReadOnlySpan<char> value, int alignment = 0, string? format = null);
public void AppendFormatted(string? value);
public void AppendFormatted(string? value, int alignment = 0, string? format = null);
public void AppendFormatted(object? value, int alignment = 0, string? format = null);
}
}
Wir ändern die Regeln für die Bedeutung von einem interpolated_string_expression (§12.8.3):
Wenn der Typ einer interpolierten Zeichenfolge string
ist und der Typ System.Runtime.CompilerServices.DefaultInterpolatedStringHandler
vorhanden ist und der aktuelle Kontext diesen Typ unterstützt, wird die Zeichenfolge mit dem Handlermuster verringert. Der letzte string
-Wert wird dann abgerufen, indem ToStringAndClear()
für den Handlertyp aufgerufen wird.Andernfalls, falls der Typ einer interpolierten Zeichenfolge System.IFormattable
oder System.FormattableString
ist [bleibt der Rest unverändert].
Die Regel "und der aktuelle Kontext unterstützt die Verwendung dieses Typs" ist absichtlich vage, um dem Compiler spielraum bei der Optimierung der Verwendung dieses Musters zu geben. Der Handlertyp ist wahrscheinlich ein ref struct Typ, und ref struct Typen sind normalerweise in asynchronen Methoden nicht zulässig. In diesem speziellen Fall kann der Compiler den Handler verwenden, wenn keine der Interpolationslöcher einen await
Ausdruck enthält, da wir statisch bestimmen können, dass der Handlertyp ohne zusätzliche komplizierte Analyse sicher verwendet wird, da der Handler nach der Auswertung des interpolierten Zeichenfolgenausdrucks abgelegt wird.
Öffnen Frage:
Möchten wir stattdessen nur den Compiler über DefaultInterpolatedStringHandler
informieren und den string.Format
Aufruf vollständig überspringen? Das würde uns ermöglichen, eine Methode auszublenden, die nicht unbedingt sichtbar sein sollte, wenn manuell string.Format
aufgerufen wird.
Antwort: Ja.
Öffnen Frage:
Möchten wir auch Handler für System.IFormattable
und System.FormattableString
haben?
Antwort: Nein.
Handlermuster-Codegenerierung
In diesem Abschnitt bezieht sich die Methodenaufrufauflösung auf die in §12.8.10.2aufgeführten Schritte.
Konstruktorauflösung
Bei einem applicable_interpolated_string_handler_typeT
und einem interpolated_string_expressioni
wird die Auflösung und Validierung des Methodenaufrufs für einen gültigen Konstruktor von T
so durchgeführt:
- Die Membersuche für Instanzkonstruktoren wird für
T
ausgeführt. Die resultierende Methodengruppe wirdM
genannt. - Die Argumentliste
A
wird wie folgt erstellt:- Die ersten beiden Argumente sind ganzzahlige Konstanten, die die Literallänge
i
bzw. die Anzahl der Interpolations-Komponenten ini
darstellen. - Wenn
i
als Argument für einen Parameterpi
in der MethodeM1
verwendet wird und der Parameterpi
mitSystem.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute
versehen ist, stimmt der Compiler für jeden NamenArgx
im ArrayArguments
dieses Attributs mit einem Parameterpx
überein, der denselben Namen hat. Die leere Zeichenfolge wird dem Empfänger vonM1
zugeordnet.- Wenn ein
Argx
nicht mit einem Parameter vonM1
abgeglichen werden kann oder einArgx
den Empfänger vonM1
anfordert undM1
eine statische Methode ist, wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt. - Andernfalls wird der Typ aller aufgelösten
px
der Argumentliste in der durch dasArguments
-Array angegebenen Reihenfolge hinzugefügt. Jedepx
wird mit derselbenref
-Semantik übergeben, wie sie inM1
angegeben ist.
- Wenn ein
- Das letzte Argument ist ein
bool
, das alsout
-Parameter übergeben wird.
- Die ersten beiden Argumente sind ganzzahlige Konstanten, die die Literallänge
- Die herkömmliche Methodenaufrufauflösung wird mit Methodengruppe
M
und ArgumentlisteA
ausgeführt. Zur endgültigen Überprüfung der Methodenausführung wird der Kontext vonM
als member_access durch den TypT
behandelt.- Wenn ein bester einzelner Konstruktor
F
gefunden wurde, istF
das Ergebnis der Überladungsauflösung. - Wenn keine anwendbaren Konstruktoren gefunden wurden, wird Schritt 3 wiederholt, um den endgültigen
bool
-Parameter ausA
zu entfernen. Wenn dieser Wiederholungsversuch auch keine anwendbaren Mitglieder findet, wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt. - Wenn keine einzige beste Methode gefunden wurde, ist das Ergebnis der Überladungsauflösung mehrdeutig, ein Fehler wird erzeugt, und es werden keine weiteren Schritte ausgeführt.
- Wenn ein bester einzelner Konstruktor
- Die endgültige Überprüfung auf
F
wird ausgeführt.- Wenn ein Element von
A
lexikalisch nachi
aufgetreten ist, wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt. - Wenn ein
A
den Empfänger vonF
anfordert undF
ein Indexer ist, der als initializer_target in einem member_initializerverwendet wird, wird ein Fehler gemeldet, und es werden keine weiteren Schritte ausgeführt.
- Wenn ein Element von
Hinweis: Die hier angegebene Auflösung verwendet absichtlich nicht die tatsächlichen Ausdrücke, die als andere Argumente für Argx
-Elemente übergeben werden. Wir betrachten nur die Typen nach der Konvertierung. Dadurch wird sichergestellt, dass es keine Probleme bei der Doppeltkonvertierung gibt oder unerwartete Fälle auftreten, in denen eine Lambda-Funktion an einen Delegatentyp gebunden ist, wenn sie an M1
übergeben und bei der Übergabe an M
einen anderen Delegatentyp gebunden wird.
Hinweis: Wir melden einen Fehler für Indexer, die als Member-Initialisierer verwendet werden, aufgrund der Auswertungsreihenfolge für geschachtelte Member-Initialisierer. Betrachten Sie diesen Codeausschnitt:
var x1 = new C1 { C2 = { [GetString()] = { A = 2, B = 4 } } };
/* Lowering:
__c1 = new C1();
string argTemp = GetString();
__c1.C2[argTemp][1] = 2;
__c1.C2[argTemp][3] = 4;
Prints:
GetString
get_C2
get_C2
*/
string GetString()
{
Console.WriteLine("GetString");
return "";
}
class C1
{
private C2 c2 = new C2();
public C2 C2 { get { Console.WriteLine("get_C2"); return c2; } set { } }
}
class C2
{
public C3 this[string s]
{
get => new C3();
set { }
}
}
class C3
{
public int A
{
get => 0;
set { }
}
public int B
{
get => 0;
set { }
}
}
Die Argumente für __c1.C2[]
werden ausgewertet, bevor der Indexer-Empfänger zugeordnet wird. Obwohl wir eine Verringerung erzielen könnten, die für dieses Szenario funktioniert (entweder durch das Erstellen eines temporären Elements für __c1.C2
und die gemeinsame Nutzung bei beiden Indexer-Aufrufen oder nur beim ersten Indexer-Aufruf und die gemeinsame Nutzung des Arguments bei beiden Aufrufen), denken wir, dass jede Verringerung für das, was wir als ein schädliches Szenario betrachten, verwirrend wäre. Daher verbieten wir das Szenario vollständig.
Offene Frage:
Wenn wir einen Konstruktor anstelle von Create
verwenden, würden wir die Codegenerierung zur Laufzeit verbessern, dadurch aber das Muster geringfügig eingrenzen.
Antwort: Wir beschränken uns vorerst auf Konstruktoren. Wir können später eine allgemeine Create
-Methode hinzufügen, wenn das Szenario auftritt.
Auflösung der Append...
-Methodenüberladung
Bei einem applicable_interpolated_string_handler_typeT
und einem interpolated_string_expressioni
erfolgt die Auflösung der Überladung für eine Gruppe gültiger Append...
-Methoden für T
wie folgt:
- Wenn interpolated_regular_string_character-Komponenten in
i
vorhanden sind:- Die Membersuche in
T
mit dem NamenAppendLiteral
wird durchgeführt. Die resultierende Methodengruppe wirdMl
benannt. - Die Argumentliste
Al
wird mit einem Wertparameter vom Typstring
erstellt. - Die herkömmliche Methodenaufrufauflösung wird mit Methodengruppe
Ml
und ArgumentlisteAl
ausgeführt. Zur endgültigen Überprüfung der Methodenausführung wird der Kontext vonMl
als member_access durch eine Instanz vonT
behandelt.- Wenn eine einzelne beste Methode
Fi
gefunden wird und dabei keine Fehler aufgetreten sind, istFi
das Ergebnis der Auflösung des Methodenaufrufs. - Andernfalls wird ein Fehler gemeldet.
- Wenn eine einzelne beste Methode
- Die Membersuche in
- Für jede Interpolations-
ix
-Komponente voni
:- Die Membersuche in
T
mit dem NamenAppendFormatted
wird durchgeführt. Die resultierende Methodengruppe wirdMf
genannt. - Die Argumentliste
Af
wird erstellt:- Der erste Parameter ist
expression
vonix
, übergeben als Wert. - Wenn
ix
direkt eine constant_expression Komponente enthält, wird ein ganzzahliger Wertparameter hinzugefügt, wobei der Namealignment
angegeben ist. - Wenn
ix
direkt auf ein interpolation_format folgt, wird ein Zeichenfolgenwert-Parameter hinzugefügt, wobei der Nameformat
angegeben wird.
- Der erste Parameter ist
- Die herkömmliche Methodenaufrufauflösung wird mit Methodengruppe
Mf
und ArgumentlisteAf
ausgeführt. Zur endgültigen Überprüfung der Methodenausführung wird der Kontext vonMf
als member_access durch eine Instanz vonT
behandelt.- Wenn eine einzelne beste Methode
Fi
gefunden wird, istFi
das Ergebnis der Auflösung des Methodenaufrufs. - Andernfalls wird ein Fehler gemeldet.
- Wenn eine einzelne beste Methode
- Die Membersuche in
- Schließlich wird für jede in den Schritten 1 und 2 ermittelte
Fi
die endgültige Überprüfung durchgeführt:- Wenn
Fi
nichtbool
nach Wert odervoid
zurückgibt, wird ein Fehler gemeldet. - Wenn alle
Fi
nicht denselben Typ zurückgeben, wird ein Fehler gemeldet.
- Wenn
Beachten Sie, dass diese Regeln keine Erweiterungsmethoden für die Append...
Aufrufe zulassen. Wir könnten dies in Betracht ziehen, wenn wir uns dafür entscheiden, aber dies ist analog zum Enumeratormuster, bei dem wir erlauben, dass GetEnumerator
eine Erweiterungsmethode ist, aber nicht Current
oder MoveNext()
.
Diese Regeln erlauben Standardparameter für die Append...
Aufrufe, die mit Dingen wie CallerLineNumber
oder CallerArgumentExpression
funktionieren (wenn von der Sprache unterstützt).
Es gibt separate Überladungssuchregeln für Basiselemente im Vergleich zu Interpolationslöchern, da einige Handler den Unterschied zwischen den komponenten verstehen möchten, die interpoliert wurden, und den Komponenten, die Teil der Basiszeichenfolge waren.
Offene Frage
Einige Szenarien, z. B. die strukturierte Protokollierung, möchten Namen für Interpolationselemente bereitstellen können. Beispielsweise könnte ein Protokollierungsaufruf heute wie Log("{name} bought {itemCount} items", name, items.Count);
aussehen. Die Namen innerhalb der {}
stellen wichtige Strukturinformationen für Logger bereit, mit denen sichergestellt wird, dass die Ausgabe konsistent und einheitlich ist. In einigen Fällen kann die :format
Komponente eines Interpolationslochs dafür wiederverwendet werden, aber viele Logger verstehen bereits Formatbezeichner und verfügen über vorhandenes Verhalten für die Ausgabeformatierung basierend auf diesen Informationen. Gibt es eine Syntax, mit der wir diese benannten Bezeichner einfügen können?
In einigen Fällen reicht möglicherweise CallerArgumentExpression
aus, vorausgesetzt, dass die Unterstützung in C# 10 verfügbar ist. Aber für Fälle, die eine Methode/Eigenschaft aufrufen, reicht dies möglicherweise nicht aus.
Antwort:
Obwohl es einige interessante Teile für vorlagenbasierte Zeichenfolgen gibt, die wir in einem orthogonalen Sprachfeature untersuchen könnten, sind wir nicht der Ansicht, dass eine bestimmte Syntax hier viele Vorteile gegenüber Lösungen wie der Verwendung eines Tupels hat: $"{("StructuredCategory", myExpression)}"
.
Durchführen der Konvertierung
Bei einem applicable_interpolated_string_handler_typeT
und einer interpolated_string_expressioni
, für die ein gültiger Konstruktor Fc
und die Append...
-Methoden Fa
aufgelöst wurden, erfolgt die Verringerung für i
wie folgt:
- Alle Argumente für
Fc
, die lexikalisch vori
auftreten, werden ausgewertet und in der lexikalischen Reihenfolge in temporäre Variablen gespeichert. Um die lexikalische Reihenfolge beizubehalten, wenni
als Teil eines größeren Ausdruckse
aufgetreten ist, werden alle Komponenten vone
, die vori
aufgetreten sind, ebenfalls in lexikalischer Reihenfolge ausgewertet. -
Fc
wird mit der Länge der Komponenten des interpolierten Zeichenfolgenliterals, der Anzahl der Interpolations-Lücken, allen zuvor ausgewerteten Argumenten und einembool
-out-Argument aufgerufen (wennFc
mit einem Argument als letzter Parameter aufgelöst wurde). Das Ergebnis wird in einem temporären Wertib
gespeichert.- Die Länge der Literalkomponenten wird berechnet, nachdem jede open_brace_escape_sequence durch ein einzelnes
{
-Element und jede close_brace_escape_sequence durch ein einzelnes}
-Element ersetzt wurde.
- Die Länge der Literalkomponenten wird berechnet, nachdem jede open_brace_escape_sequence durch ein einzelnes
- Wenn
Fc
mit einembool
-out-Argument beendet wurde, wird eine Überprüfung desbool
-Wertes generiert. Wenn wahr, werden die Methoden inFa
aufgerufen. Andernfalls werden sie nicht aufgerufen. - Für jede
Fax
inFa
wird fürib
je nach BedarfFax
entweder mit der aktuellen Literalkomponente oder dem Interpolations-Ausdruck aufgerufen. WennFax
einenbool
-Wert zurückgibt, wird das Ergebnis logisch mit allen vorherigenFax
-Aufrufen per UND-Operator verbunden.- Wenn
Fax
ein Aufruf vonAppendLiteral
ist, wird die Literalkomponente entschlüsselt, indem jede open_brace_escape_sequence durch ein einzelnes{
-Element und jede close_brace_escape_sequence durch ein einzelnes}
-Element ersetzt wird.
- Wenn
- Das Ergebnis der Konvertierung ist
ib
.
Beachten Sie erneut, dass die an Fc
und e
übergebenen Argumente dasselbe temporäre Element sind. Konvertierungen können basierend auf diesem temporären Element erfolgen, um sie in ein von Fc
benötigtes Format zu bringen, aber beispielsweise können Lambdas nicht an einen anderen Delegatentyp zwischen Fc
und e
gebunden werden.
Offene Frage
Das bedeutet, dass nachfolgende Teile der interpolierten Zeichenfolge nach einem falsch zurückgegebenen Append...
-Aufruf nicht ausgewertet werden. Dies könnte möglicherweise sehr verwirrend sein, insbesondere wenn das Formatloch Nebenwirkungen hat. Stattdessen könnten wir zuerst alle Formatlöcher auswerten und dann wiederholt Append...
mit den Ergebnissen aufrufen und beenden, wenn "false" zurückgegeben wird. Dadurch wird sichergestellt, dass alle Ausdrücke wie erwartet ausgewertet werden, aber wir rufen so wenige Methoden wie nötig auf. Obwohl die Teilbewertung für einige komplexere Fälle wünschenswert sein könnte, ist dies für den allgemeinen Fall vielleicht nicht intuitiv.
Eine weitere Alternative, wenn wir immer alle Formatlöcher auswerten möchten, besteht darin, die Append...
Version der API zu entfernen und nur wiederholte Format
Aufrufe auszuführen. Der Handler kann nachverfolgen, ob er einfach das Argument ablegen und sofort für diese Version zurückgeben soll.
Antwort: Wir werden die Lücken bedingt bewerten.
Offene Frage
Müssen wir verfügbare Handlertypen löschen und Aufrufe mit „try/finally“ umschließen, um sicherzustellen, dass „Dispose“ aufgerufen wird? Beispielsweise könnte der interpolierte Zeichenfolgenhandler in der Basisklassenbibliothek (BCL) über ein gemietetes Array verfügen, und wenn eine der Interpolationslücken während der Auswertung eine Ausnahme auslöst, könnte dieses gemietete Array verloren gehen, wenn es nicht gelöscht wurde.
Antwort: Nein. Handler können lokal zugewiesen werden (z. B. MyHandler handler = $"{MyCode()};
), und die Lebensdauer solcher Handler ist unklar. Das steht im Gegensatz zu einem Foreach-Enumerator, bei dem die Lebensdauer offensichtlich ist und keine benutzerdefinierte lokale Variable für den Enumerator erstellt wird.
Auswirkungen auf Verweistypen, die Null-Werte zulassen
Um die Komplexität der Implementierung zu minimieren, haben wir einige Einschränkungen, wie wir nullable Analysen für interpolierte Zeichenfolgenhandlerkonstruktoren ausführen, die als Argumente für eine Methode oder einen Indexer verwendet werden. Insbesondere leiten wir keine Informationen vom Konstruktor zurück an die ursprünglichen Slots von Parametern oder Argumenten des ursprünglichen Kontexts. Außerdem verwenden wir keine Konstruktorparametertypen, um generische Typableitungen für Typparameter in der enthaltenden Methode heranzuziehen. Ein Beispiel dafür, wo sich dies auswirken kann, ist:
string s = "";
C c = new C();
c.M(s, $"", c.ToString(), s.ToString()); // No warnings on c.ToString() or s.ToString(), as the `MaybeNull` does not flow back.
public class C
{
public void M(string s1, [InterpolatedStringHandlerArgument("", "s1")] CustomHandler c1, string s2, string s3) { }
}
[InterpolatedStringHandler]
public partial struct CustomHandler
{
public CustomHandler(int literalLength, int formattedCount, [MaybeNull] C c, [MaybeNull] string s) : this()
{
}
}
string? s = null;
M(s, $""); // Infers `string` for `T` because of the `T?` parameter, not `string?`, as flow analysis does not consider the unannotated `T` parameter of the constructor
void M<T>(T? t, [InterpolatedStringHandlerArgument("s1")] CustomHandler<T> c) { }
[InterpolatedStringHandler]
public partial struct CustomHandler<T>
{
public CustomHandler(int literalLength, int formattedCount, T t) : this()
{
}
}
Weitere Überlegungen
Zulassen, dass string
-Typen auch in Handler konvertiert werden können
Aus Gründen der Einfachheit des Auftragstyps könnten wir erwägen, dass Ausdrücke vom Typ string
implizit in applicable_interpolated_string_handler_types konvertiert werden können. Wie heute vorgeschlagen, müssen Autoren wahrscheinlich sowohl diesen Handlertyp als auch reguläre string
-Typen überladen, damit ihre Benutzer den Unterschied nicht kennen müssen. Dies kann ein lästiger und nicht offensichtlicher Aufwand sein, weil ein string
-Ausdruck als Interpolation mit vorab ausgefüllter Länge expression.Length
und 0 auszufüllenden Lücken angesehen werden kann.
Dies würde es neuen APIs ermöglichen, nur einen Handler bereitzustellen, ohne auch eine Überladung, die string
akzeptiert, verfügbar machen zu müssen. Es bleibt jedoch weiter notwendig, Änderungen für eine bessere Konvertierung von Ausdrücken vorzunehmen, sodass es möglicherweise unnötiger Aufwand ist.
Antwort:
Wir denken, dass dies verwirrend sein könnte, und es gibt eine einfache Lösung für benutzerdefinierte Handlertypen: Fügen Sie eine benutzerdefinierte Umwandlung von einem String hinzu.
Integrieren von span-Elementen für Zeichenfolgen ohne Heap
ValueStringBuilder
, wie er heute existiert, verfügt über 2 Konstruktoren: einen, der eine Anzahl akzeptiert und vorzeitig im Heap speichert, sowie einen, der ein Span<char>
akzeptiert. Dieses Span<char>
-Element ist in der Regel eine feste Größe in der Laufzeit-Codebasis, etwa 250 Elemente im Durchschnitt. Um diesen Typ wirklich zu ersetzen, sollten wir eine Erweiterung darauf in Betracht ziehen, bei der wir auch GetInterpolatedString
-Methoden berücksichtigen, die ein Span<char>
-Element verwenden, anstatt nur die Zählversion. Hier sehen wir jedoch einige potenzielle knifflige Fälle.
- Wir möchten in einer Schleife in einer heißen Ebene nicht wiederholt „stackalloc“ verwenden. Wenn diese Erweiterung für das Feature ausgeführt wird, würden wir wahrscheinlich die mithilfe von „stackalloc“ erstellte Spanne zwischen Schleifeniterationen teilen. Wir wissen, dass dies sicher ist, da
Span<T>
eine Referenzstruktur ist, die nicht auf dem Heap gespeichert werden kann, und Benutzer müssten ziemlich unabsichtig sein, um einen Verweis auf dieseSpan
zu extrahieren (z. B. das Erstellen einer Methode, die einen solchen Handler akzeptiert, und dann absichtlich denSpan
aus dem Handler abruft und an den Aufrufer zurückgibt). Die Zuweisung vor der Zeit führt jedoch zu anderen Fragen:- Sollten wir stackalloc bereitwillig verwenden? Was geschieht, wenn die Schleife nie aufgerufen oder beendet wird, bevor sie den Platz benötigt?
- Wenn wir „stackalloc“ nicht vorzeitig verwenden, bedeutet das dann, dass wir in jeder Schleife eine verdeckte Verzweigung einführen? Für die meisten Schleifen ist dies wahrscheinlich nicht relevant, aber es könnte sich auf einige enge Schleifen auswirken, die die Kosten nicht tragen möchten.
- Einige Zeichenfolgen können ziemlich groß sein, und der entsprechende Betrag für
stackalloc
hängt von einer Reihe von Faktoren ab, einschließlich Laufzeitfaktoren. Wir möchten nicht, dass der C#-Compiler und die Spezifikation diese vorab ermitteln müssen. Daher möchten wir https://github.com/dotnet/runtime/issues/25423 auflösen und eine API für den Compiler hinzufügen, die in diesen Fällen aufgerufen werden soll. Zudem werden den Punkten aus der vorherigen Schleife weitere Vor- und Nachteile hinzugefügt. Dabei möchten wir gegebenenfalls vermeiden, mehrfach große Arrays dem Heap zuzuweisen, insbesondere bevor einer davon benötigt wird.
Antwort:
Dies liegt außerhalb des Gültigkeitsbereichs für C# 10. Wir können dies im Allgemeinen sehen, wenn wir das allgemeinere params Span<T>
-Feature betrachten.
Nicht-Testversion der API
Aus Gründen der Einfachheit schlägt diese Spezifikation derzeit nur vor, eine Append...
-Methode zu erkennen, und Elemente, die immer erfolgreich sind (wie InterpolatedStringHandler
), würden aus der Methode immer den Wert „true“ zurückgeben.
Dies wurde getan, um Teilformatierungsszenarien zu unterstützen, in denen der Benutzer die Formatierung beenden möchte, wenn ein Fehler auftritt oder wenn er unnötig ist, z. B. der Protokollierungsfall, aber möglicherweise eine Reihe unnötiger Verzweigungen in der standardmäßigen interpolierten Zeichenfolgenverwendung einführen könnte. Wir könnten einen Zusatz in Betracht ziehen, bei dem wir nur FormatX
Methoden verwenden, wenn keine Append...
Methode vorhanden ist, aber es stellt Fragen darüber dar, was wir tun, wenn eine Mischung aus Append...
und FormatX
Aufrufen vorhanden ist.
Antwort:
Wir möchten die Nicht-Try-Version der API. Der Vorschlag wurde aktualisiert, um dies widerzuspiegeln.
Übergeben vorheriger Argumente an den Handler
Es besteht zurzeit ein unglücklicher Mangel an Symmetrie im Vorschlag: Das Aufrufen einer Erweiterungsmethode in reduzierter Form erzeugt eine andere Semantik als das Aufrufen der Erweiterungsmethode in normaler Form. Dies unterscheidet sich von den meisten anderen Orten in der Sprache, wo reduzierte Form nur ein Zucker ist. Wir schlagen vor, dem Framework ein Attribut hinzuzufügen, das beim Binden einer Methode erkannt wird, die den Compiler darüber informiert, dass bestimmte Parameter an den Konstruktor im Handler übergeben werden sollen. Die Verwendung sieht wie folgt aus:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedStringHandlerArgumentAttribute : Attribute
{
public InterpolatedStringHandlerArgumentAttribute(string argument);
public InterpolatedStringHandlerArgumentAttribute(params string[] arguments);
public string[] Arguments { get; }
}
}
Die Verwendung ist dann:
namespace System
{
public sealed class String
{
public static string Format(IFormatProvider? provider, [InterpolatedStringHandlerArgument("provider")] ref DefaultInterpolatedStringHandler handler);
…
}
}
namespace System.Runtime.CompilerServices
{
public ref struct DefaultInterpolatedStringHandler
{
public DefaultInterpolatedStringHandler(int baseLength, int holeCount, IFormatProvider? provider); // additional factory
…
}
}
var formatted = string.Format(CultureInfo.InvariantCulture, $"{X} = {Y}");
// Is lowered to
var tmp1 = CultureInfo.InvariantCulture;
var handler = new DefaultInterpolatedStringHandler(3, 2, tmp1);
handler.AppendFormatted(X);
handler.AppendLiteral(" = ");
handler.AppendFormatted(Y);
var formatted = string.Format(tmp1, handler);
Die Fragen, die wir beantworten müssen:
- Gefällt uns dieses Muster im Allgemeinen?
- Möchten wir zulassen, dass diese Argumente nach dem Handlerparameter stammen? Einige vorhandene Muster in der BCL, z. B.
Utf8Formatter
, positionieren den Wert, der formatiert werden soll, vor dem Element, in das formatiert werden soll. Um sich am besten an diese Muster anzupassen, würden wir dies wahrscheinlich zulassen, aber wir müssen entscheiden, ob diese Bewertung außerhalb der Reihenfolge in Ordnung ist.
Antwort:
Wir wollen dies unterstützen. Die Spezifikation wurde aktualisiert, um dies widerzuspiegeln. Argumente müssen in lexikalischer Reihenfolge an der Aufrufstelle angegeben werden, und wenn ein erforderliches Argument für die Methode zum Erstellen nach dem interpolierten Zeichenfolgenliteral angegeben wird, wird ein Fehler angezeigt.
await
-Verwendung in Interpolationslücken
Da $"{await A()}"
heute ein gültiger Ausdruck ist, müssen wir Interpolationslücken mit „await“ rationalisieren. Wir könnten dies mit einigen Regeln lösen:
- Wenn eine interpolierte Zeichenfolge, die als
string
,IFormattable
oderFormattableString
verwendet wird,await
in einer Interpolationslücke aufweist, gibt es ein Fallback auf den Formatierer im alten Stil. - Wenn eine interpolierte Zeichenfolge einer implicit_string_handler_conversion unterliegt und applicable_interpolated_string_handler_type ein
ref struct
ist, darfawait
nicht in den Formatlücken verwendet werden.
Grundsätzlich könnte dieses Desugaring eine Referenzstruktur in einer asynchronen Methode verwenden, solange sichergestellt ist, dass ref struct
nicht im Heap gespeichert werden muss. Das sollte möglich sein, wenn await
s in den Interpolationslücken verboten werden.
Alternativ können wir einfach alle Handlertypen erstellen, die keine Verweisstruktur aufweisen, einschließlich des Frameworkhandlers für interpolierte Zeichenfolgen. Dies würde uns jedoch daran hindern, eines Tages eine Span
-Version zu erkennen, die überhaupt keinen Zwischenspeicher zuweisen muss.
Antwort:
Interpolierte Zeichenfolgenhandler werden wie alle anderen Typen behandelt: Dies bedeutet, dass die Verwendung von Handlern hier unzulässig ist, wenn der Handlertyp eine Verweisstruktur ist und der aktuelle Kontext die Verwendung von Verweisstrukturs nicht zulässt. Die Spezifikation zum Verringern von Zeichenfolgenliteralen, die als Zeichenfolgen verwendet werden, ist absichtlich vage. Denn so kann der Compiler entscheiden, welche Regeln er als angemessen erachtet, aber für benutzerdefinierte Handlertypen muss er dieselben Regeln wie die restliche Sprache befolgen.
Handler als Referenzparameter
Einige Handler sollten unter Umständen als Referenzparameter übergeben werden (entweder in
oder ref
). Sollten wir eine der beiden zulassen? Und wenn ja, wie sieht ein ref
-Handler aus? ref $""
ist verwirrend, da Sie die Zeichenfolge nicht tatsächlich per Verweis übergeben. Stattdessen wird der Handler übergeben, der aus dem Verweis erstellt wird, und dieser hat ähnliche potenzielle Probleme bei asynchronen Methoden.
Antwort:
Wir wollen dies unterstützen. Die Spezifikation wurde aktualisiert, um dies widerzuspiegeln. Die Regeln sollten dieselben Regeln enthalten, die für Erweiterungsmethoden für Werttypen gelten.
Interpolierte Zeichenfolgen unter Verwendung binärer Ausdrücke und Konvertierungen
Weil dieser Vorschlag interpolierte Zeichenfolgen kontextabhängig macht, möchten wir dem Compiler erlauben, entweder einen binären Ausdruck, der vollständig aus interpolierten Zeichenfolgen besteht, oder eine interpolierte Zeichenfolge, die einer Umwandlung unterliegt, als ein interpoliertes Zeichenfolgenliteral für die Überladungsauflösung zu behandeln. Nehmen Sie sich beispielsweise das folgende Szenario an:
struct Handler1
{
public Handler1(int literalLength, int formattedCount, C c) => ...;
// AppendX... methods as necessary
}
struct Handler2
{
public Handler2(int literalLength, int formattedCount, C c) => ...;
// AppendX... methods as necessary
}
class C
{
void M(Handler1 handler) => ...;
void M(Handler2 handler) => ...;
}
c.M($"{X}"); // Ambiguous between the M overloads
Dies wäre mehrdeutig, was eine Umwandlung entweder zu Handler1
oder Handler2
erfordert, um das Problem zu lösen. Wenn wir jedoch diese Umwandlung vornehmen, würden wir die Informationen, die es im Zusammenhang mit dem Methodenempfänger gibt, möglicherweise wegwerfen. Das bedeutet, dass die Umwandlung scheitert, weil es nichts gibt, um die Informationen von c
auszufüllen. Ein ähnliches Problem tritt bei der binären Verkettung von Zeichenfolgen auf: Der Benutzer möchte das Literal über mehrere Zeilen hinweg formatieren, um einen Zeilenumbruch zu vermeiden, kann das aber nicht, weil dies kein interpoliertes Zeichenfolgenliteral ist, das in den Handlertyp konvertierbar ist.
Um diese Fälle zu beheben, nehmen wir die folgenden Änderungen vor:
- Ein additive_expression, der ausschließlich aus interpolated_string_expressions besteht und nur
+
-Operatoren verwendet, gilt als interpolated_string_literal für Konvertierungen und die Überladungsauflösung. Die endgültige interpolierte Zeichenfolge wird erstellt, indem alle einzelnen interpolated_string_expression-Komponenten logisch von links nach rechts verkettet werden. - Ein cast_expression oder ein relational_expression mit Operator
as
, dessen Operand interpolated_string_expressions ist, gilt als interpolated_string_expressions für Konvertierungen und die Überladungsauflösung.
Offene Fragen:
Möchten wir dies tun? Wir tun dies nicht für System.FormattableString
, aber das kann auf eine andere Zeile aufgeteilt werden, während dies kontextabhängig sein kann und daher nicht in eine andere Zeile unterteilt werden kann. Es gibt auch keine Bedenken hinsichtlich der Überlastungslösung bei FormattableString
und IFormattable
.
Antwort:
Wir glauben, dass dies ein gültiger Anwendungsfall für additive Ausdrücke ist, aber dass die Umwandlungsversion derzeit nicht überzeugend genug ist. Wir können es später bei Bedarf hinzufügen. Die Spezifikation wurde aktualisiert, um diese Entscheidung widerzuspiegeln.
Andere Anwendungsfälle
Beispiele für vorgeschlagene Handler-APIs mit diesem Muster finden Sie unter https://github.com/dotnet/runtime/issues/50635.
C# feature specifications