From 368a518385480faf132997aa86b874340b96a02a Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 25 Jan 2026 12:59:14 -0500 Subject: [PATCH 1/7] support improved interpolated strings --- standard/conversions.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/standard/conversions.md b/standard/conversions.md index 5b119b3b7..e260cb19d 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -146,8 +146,7 @@ An implicit enumeration conversion permits a *constant_expression* ([§12.26](ex ### 10.2.5 Implicit interpolated string conversions -An implicit interpolated string conversion permits an *interpolated_string_expression* ([§12.8.3](expressions.md#1283-interpolated-string-expressions)) to be converted to `System.IFormattable` or `System.FormattableString` (which implements `System.IFormattable`). -When this conversion is applied, a string value is not composed from the interpolated string. Instead an instance of `System.FormattableString` is created, as further described in [§12.8.3](expressions.md#1283-interpolated-string-expressions). +For any type `T` that is an applicable interpolated string handler type (§custInterpStrExpCustHandling), there exists an implicit interpolated string handler conversion to `T` from a non-constant *ISE* ([§12.8.3](expressions.md#1283-interpolated-string-expressions)). This conversion exists, regardless of whether errors are found later when attempting to lower the interpolation using the handler pattern. This ensures that there are predictable and useful errors, and that runtime behavior doesn't change based on the content of an interpolated string. ### 10.2.6 Implicit nullable conversions From f07d1e23662747c9d24fbbda0b99137d321b1ce0 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 25 Jan 2026 13:12:26 -0500 Subject: [PATCH 2/7] support improved interpolated strings --- standard/expressions.md | 48 ++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index d73b34946..4499dadb5 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1052,6 +1052,7 @@ A function member is said to be an ***applicable function member*** with respect - Each argument in `A` corresponds to a parameter in the function member declaration as described in [§12.6.2.2](expressions.md#12622-corresponding-parameters), at most one argument corresponds to each parameter, and any parameter to which no argument corresponds is an optional parameter. - For each argument in `A`, the parameter-passing mode of the argument is identical to the parameter-passing mode of the corresponding parameter, and - for a value parameter or a parameter array, an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)) exists from the argument expression to the type of the corresponding parameter, or + - for a reference parameter whose type is a struct type, an implicit interpolated string handler conversion exists from the argument to the type of the corresponding parameter, or - for a reference or output parameter, there is an identity conversion between the type of the argument expression (if any) and the type of the corresponding parameter, or - for an input parameter when the corresponding argument has the `in` modifier, there is an identity conversion between the type of the argument expression (if any) and the type of the corresponding parameter, or - for an input parameter when the corresponding argument omits the `in` modifier, an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)) exists from the argument expression to the type of the corresponding parameter. @@ -1365,7 +1366,13 @@ A *primary_expression* that consists of a *literal* ([§6.4.5](lexical-structure ### 12.8.3 Interpolated string expressions -An *interpolated_string_expression* consists of `$`, `$@`, or `@$`, immediately followed by text within `"` characters. Within the quoted text there are zero or more ***interpolations*** delimited by `{` and `}` characters, each of which encloses an *expression* and optional formatting specifications. +An *interpolated_string_expression* consists of `$`, `$@`, or `@$`, immediately followed by text within `"` characters. Within the quoted text there are zero or more ***interpolations*** delimited by `{` and `}` characters, each of which encloses an *expression* and optional formatting specifications. Any quoted text that is not part of an interpolation (as defined by the grammar rules *Interpolated_Regular_String_Mid* and *Interpolated_Verbatim_String_Mid* shown below) is part of an *interpolated string expression segment*. As such, the quoted text contains zero or more interpolated string expression segments. Consider the following *interpolated_string_expression*: + +```csharp +$"val = {{{val,4:X}}}; 2 * val = {2 * val}." +``` + +This contains the interpolated string expression segments `"val = {"`, `"}; 2 * val = "`, and `"."`, the first of which factors in the presence of the open/close brace escape sequences described in the grammar below. The quoted text also contains the interpolations `"{val,4:X}"` and `"{2 * val}"`. Interpolated string expressions have two forms; regular (*interpolated_regular_string_expression*) and verbatim (*interpolated_verbatim_string_expression*); which are lexically similar to, but differ semantically from, the two forms of string @@ -1499,13 +1506,31 @@ other tokens in the language. *end note* other lexer generators ANTLR supports context sensitive lexical rules, for example using its *lexical modes*, but this is an implementation detail and therefore not part of this specification. *end note* -An *interpolated_string_expression* is classified as a value. If it is immediately converted to `System.IFormattable` or `System.FormattableString` with an implicit interpolated string conversion ([§10.2.5](conversions.md#1025-implicit-interpolated-string-conversions)), the interpolated string expression has that type. Otherwise, it has the type `string`. +An *interpolated_string_expression* is classified as a value, which is evaluated in one of the following ways depending on the context in which it appears: -> *Note*: The differences between the possible types an *interpolated_string_expression* may be determined from the documentation for `System.String` ([§C.2](standard-library.md#c2-standard-library-types-defined-in-isoiec-23271)) and `System.FormattableString` ([§C.3](standard-library.md#c3-standard-library-types-not-defined-in-isoiec-23271)). *end note* +1. If the target of an assignment or method-call argument has type `string`, the expression is processed by the default interpolated string handler, `System.Runtime.CompilerServices.DefaultInterpolatedStringHandler`, and the result has type `string`. +1. If the target of an assignment or method-call argument has a custom interpolated string handler (§custInterpStrExpHandler) type, then + - If the interpolated string contains no interpolations, the expression is processed as if the target type was `string`. + - Otherwise, the expression is processed by the custom interpolated string handler and the result has that custom interpolated string handler’s type. -The meaning of an interpolation, both *regular_interpolation* and *verbatim_interpolation*, is to format the value of the *expression* as a `string` either according to the format specified by the *Regular_Interpolation_Format* or *Verbatim_Interpolation_Format*, or according to a default format for the type of *expression*. The formatted string is then modified by the *interpolation_minimum_width*, if any, to produce the final `string` to be interpolated into the *interpolated_string_expression*. +These rules are illustrated by the following: + +```csharp +static void M(string s) { } +static void M(SomeInterpolatedStringHandler s) { } + +int val = 255; +M($"no interpolations");// invokes M(string) +M($"{val}"); // invokes M(SomeInterpolatedStringHandler) +string s1 = $"{val}"; // default handler used, as target has type string +M(s1); // invokes M(string) +SomeInterpolatedStringHandler str2 = $"{val}"; // custom handler used +M(s2); // invokes M(SomeInterpolatedStringHandler) +``` + +The remainder of this subclause deals with the default interpolated string handler behavior only. The declaration and use of custom interpolated string handlers is described in §custInterpStrExpHandler. -> *Note*: How the default format for a type is determined is detailed in the documentation for `System.String` ([§C.2](standard-library.md#c2-standard-library-types-defined-in-isoiec-23271)) and `System.FormattableString` ([§C.3](standard-library.md#c3-standard-library-types-not-defined-in-isoiec-23271)). Descriptions of standard formats, which are identical for *Regular_Interpolation_Format* and *Verbatim_Interpolation_Format*, may be found in the documentation for `System.IFormattable` ([§C.4](standard-library.md#c4-format-specifications)) and in other types in the standard library ([§C](standard-library.md#annex-c-standard-library)). *end note* +The meaning of an interpolation, both *regular_interpolation* and *verbatim_interpolation*, is to format the value of the *expression* as a `string` either according to the format specified by the *Regular_Interpolation_Format* or *Verbatim_Interpolation_Format*, or according to a default format for the type of *expression*. The formatted string is then modified by the *interpolation_minimum_width*, if any, to produce the final `string` to be interpolated into the *interpolated_string_expression*. In an *interpolation_minimum_width* the *constant_expression* shall have an implicit conversion to `int`. Let the *field width* be the absolute value of this *constant_expression* and the *alignment* be the sign (positive or negative) of the value of this *constant_expression*: @@ -1514,9 +1539,7 @@ In an *interpolation_minimum_width* the *constant_expression* shall have an impl - If the alignment is positive the formatted string is right-aligned by prepending the padding, - Otherwise it is left-aligned by appending the padding. -The overall meaning of an *interpolated_string_expression*, including the above formatting and padding of interpolations, is defined by a conversion of the expression to a method invocation: if the type of the expression is `System.IFormattable` or `System.FormattableString` that method is `System.Runtime.CompilerServices.FormattableStringFactory.Create` ([§C.3](standard-library.md#c3-standard-library-types-not-defined-in-isoiec-23271)) which returns a value of type `System.FormattableString`; otherwise the type shall be `string` and the method is `string.Format` ([§C.2](standard-library.md#c2-standard-library-types-defined-in-isoiec-23271)) which returns a value of type `string`. - -In both cases, the argument list of the call consists of a *format string literal* with *format specifications* for each interpolation, and an argument for each expression corresponding to the format specifications. +The interpolated string expression is treated as a *format string literal* with *format specifications* for each interpolation. The format string literal is constructed as follows, where `N` is the number of interpolations in the *interpolated_string_expression*. The format string literal consists of, in order: @@ -1571,6 +1594,14 @@ Then: *end example* +A *constant interpolated string* is an *interpolated_string_expression* that contains + - no interpolations, or + - interpolations whose *expression*s are constant expressions of type `string`, and these interpolations have no *interpolation_minimum_width*, *Regular_Interpolation_Format*, or *Verbatim_Interpolation_Format* specifiers. + +For example, $"Hello", $"{cs1}, world!" (given `const string cs1 = $"Hello";`), $"{"Hello," + $"world!"}", and $"xxx{(true ? $"{"X"}" : $"{$"{"Y"}"}")}yyy" are all constant interpolated strings. However, $"{123}" and $"{"abc"}{123.45}" are not. + +For simplicity, the term *ISE* is used throughout this specification to mean either an *interpolated_string_expression* or an *additive_expression* composed entirely of *interpolated_string_expression*s and binary `+` operators. + ### 12.8.4 Simple names A *simple_name* consists of an identifier, optionally followed by a type argument list: @@ -7169,6 +7200,7 @@ A *constant_expression* of type `nint` shall have a value in the range \[`int.Mi Only the following constructs are permitted in constant expressions: - Literals (including the `null` literal). +- Constant interpolated strings. - References to `const` members of class, struct, and interface types. - References to members of enumeration types. - References to local constants. From 8581c5ff3632ff8c22ddc689a11a115e4c69faa8 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 25 Jan 2026 14:17:42 -0500 Subject: [PATCH 3/7] support improved interpolated strings --- standard/standard-library.md | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/standard/standard-library.md b/standard/standard-library.md index 5c46468f3..28f01b3a8 100644 --- a/standard/standard-library.md +++ b/standard/standard-library.md @@ -784,6 +784,31 @@ namespace System.Linq.Expressions namespace System.Runtime.CompilerServices { + public ref struct DefaultInterpolatedStringHandler + { + public DefaultInterpolatedStringHandler(int literalLength, + int formattedCount); + public DefaultInterpolatedStringHandler(int literalLength, + int formattedCount, IFormatProvider? provider); + public DefaultInterpolatedStringHandler(int literalLength, + int formattedCount, IFormatProvider? provider, Span initialBuffer); + public void AppendFormatted(scoped ReadOnlySpan value); + public void AppendFormatted(string? value); + public void AppendFormatted(object? value, int alignment = 0, + string? format = default); + public void AppendFormatted(scoped ReadOnlySpan value, + int alignment = 0, string? format = default); + public void AppendFormatted(string? value, int alignment = 0, + string? format = default); + public void AppendFormatted(T value); + public void AppendFormatted(T value, int alignment); + public void AppendFormatted(T value, string? format); + public void AppendFormatted(T value, int alignment, string? format); + public void AppendLiteral(string value); + public override string ToString(); + public string ToStringAndClear(); + } + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] @@ -834,6 +859,21 @@ namespace System.Runtime.CompilerServices void OnCompleted(Action continuation); } + [System.AttributeUsage(System.AttributeTargets.Parameter, + AllowMultiple=false, Inherited=false)] + public sealed class InterpolatedStringHandlerArgumentAttribute : Attribute + { + public InterpolatedStringHandlerArgumentAttribute(string argument); + public InterpolatedStringHandlerArgumentAttribute(params string[] arguments); + } + + [System.AttributeUsage(System.AttributeTargets.Class | + System.AttributeTargets.Struct, AllowMultiple=false, Inherited=false)] + public sealed class InterpolatedStringHandlerAttribute : Attribute + { + public InterpolatedStringHandlerAttribute (); + } + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class ModuleInitializerAttribute : Attribute { @@ -1397,10 +1437,13 @@ The following library types are referenced in this specification. The full names - `global::System.Runtime.CompilerServices.CallerFileAttribute` - `global::System.Runtime.CompilerServices.CallerLineNumberAttribute` - `global::System.Runtime.CompilerServices.CallerMemberNameAttribute` +- `global::System.Runtime.CompilerServices.DefaultInterpolatedStringHandler` - `global::System.Runtime.CompilerServices.FormattableStringFactory` - `global::System.Runtime.CompilerServices.ICriticalNotifyCompletion` - `global::System.Runtime.CompilerServices.IndexerNameAttribute` - `global::System.Runtime.CompilerServices.INotifyCompletion` +- `global::System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute` +- `global::System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute` - `global::System.Runtime.CompilerServices.ModuleInitializerAttribute` - `global::System.Runtime.CompilerServices.TaskAwaiter` - `global::System.Runtime.CompilerServices.TaskAwaiter` From ffee71afdb56c86a3037b14d943f9a3d85f4c66c Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 25 Jan 2026 14:25:50 -0500 Subject: [PATCH 4/7] support improved interpolated strings --- standard/attributes.md | 182 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/standard/attributes.md b/standard/attributes.md index f330acba9..a6eac3c37 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -497,6 +497,7 @@ A number of attributes affect the language in some way. These attributes include - `System.Runtime.CompilerServices.CallerLineNumberAttribute` ([§23.5.6.2](attributes.md#23562-the-callerlinenumber-attribute)), `System.Runtime.CompilerServices.CallerFilePathAttribute` ([§23.5.6.3](attributes.md#23563-the-callerfilepath-attribute)), and `System.Runtime.CompilerServices.CallerMemberNameAttribute` ([§23.5.6.4](attributes.md#23564-the-callermembername-attribute)), which are used to supply information about the calling context to optional parameters. - `System.Runtime.CompilerServices.EnumeratorCancellationAttribute` ([§23.5.8](attributes.md#2358-the-enumeratorcancellation-attribute)), which is used to specify parameter for the cancellation token in an asynchronous iterator. - `System.Runtime.CompilerServices.ModuleInitializer` ([§23.5.9](attributes.md#2359-the-moduleinitializer-attribute)), which is used to mark a method as a module initializer. +- `System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute` and `System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute`, which are used to declare a custom interpolated string expression handler (§custInterpStrExpHandler) and to call one of its constructors, respectively. The Nullable static analysis attributes ([§23.5.7](attributes.md#2357-code-analysis-attributes)) can improve the correctness of warnings generated for nullabilities and null states ([§8.9.5](types.md#895-nullabilities-and-null-states)). @@ -1173,6 +1174,187 @@ A module initializer shall have the following characteristics: - Be accessible from the containing module (that is, have an access modifier `internal` or `public`). - Not be a local function. +#### §custInterpStrExpHandler Custom interpolated string expression handlers + +##### §custInterpStrExpCustHandling Declaring a custom handler + +Consider the following program, which implements a simple message logger: + + +```csharp +using System; +public class Logger +{ + public void LogMessage(string msg) + { + Console.WriteLine(msg); + } +} +public class Program +{ + static void Main() + { + var logger = new Logger(); + int val = 255; + logger.LogMessage($"val = {{{val,4:X}}}; 2 * val = {2 * val}."); + } +} +``` + +The output produced is, as follows: + +```console +val = { FF}; 2 * val = 510. +``` + +In the call to `LogMessage`, the target of the interpolated string expression argument is parameter `msg`, which has type `string`. As such, according to [§12.8.3](expressions.md#1283-interpolated-string-expressions), the default interpolated string expression handler is invoked. The following subclause (§custInterpStrExpCustHandling) shows how to use a custom handler. + +In order to provide custom processing to the program shown in §custInterpStrExpDefHandling, a *custom interpolated string expression handler* is needed. Here then is the message logger with a custom handler added (which although it does nothing more than behave like the default handler, it provides the hooks for customization): + + +```csharp +using System; +using System.Text; +using System.Runtime.CompilerServices; + +[InterpolatedStringHandler] +public ref struct LogInterpolatedStringHandler +{ + StringBuilder builder; // Storage for the built-up string + public LogInterpolatedStringHandler(int literalLength, int formattedCount) + { + builder = new StringBuilder(literalLength); + } + public void AppendLiteral(string s) + { + builder.Append(s); + } + public void AppendFormatted(T t) + { + builder.Append(t?.ToString()); + } + public void AppendFormatted(T t, string format) where T : IFormattable + { + builder.Append(t?.ToString(format, null)); + } + public void AppendFormatted(T t, int alignment, string format) + where T : IFormattable + { + builder.Append(String.Format("{0" + "," + alignment + ":" + format + "}", t)); + } + public override string ToString() => builder.ToString(); +} + +public class Logger +{ + public void LogMessage(string msg) + { + Console.WriteLine(msg); + } + public void LogMessage(LogInterpolatedStringHandler builder) + { + Console.WriteLine(builder.ToString()); + } +} + +public class Program +{ + static void Main() + { + var logger = new Logger(); + int val = 255; + logger.LogMessage($"val = {{{val,4:X}}}; 2 * val = {2 * val}."); + } +} +``` + +The output produced is, as follows: + +```console +val = { FF}; 2 * val = 510. +``` + +A type having the attribute `System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute` is said to be an *applicable interpolated string handler type*. + +To qualify as a custom interpolated string expression handler, a class or struct type shall have the following characteristics: + +- Be marked with the attribute `System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute`. +- Have an accessible constructor whose first two parameters have type `int`. (Other parameters may follow, which are used to pass information to/from the handler. These are discussed in §custInterpStrExpPassInfo. An optional final parameter may be declared to inhibit the handler from processing the interpolated string. This is discussed in §custInterpStrExpInhibCustHandler). + +When the compiler-generated code calls the constructor, the first parameter is set to the sum of the lengths of the interpolated string expression segments ([§12.8.3](expressions.md#1283-interpolated-string-expressions)) in the interpolated string expression, and the second parameter is set to the number of interpolations. (For `($"val = {{{val,4:X}}}; 2 * val = {2 * val}."`, these values are 21 and 2, respectively.) + +- Have an accessible method with the signature `void AppendLiteral(string s)`, which is called to process a single interpolated string expression literal segment. +- Have a set of accessible overloaded methods called `AppendFormatted`, one of which is called to process a single interpolation, based on that interpolation’s content. Their signatures are, as follows: + - `void AppendFormatted(T t)`, which deals with interpolations having no explicit format or alignment, as in the case of `{2 * val}`. + - `void AppendFormatted(T t, string format) where T : System.IFormattable`, which deals with interpolations having an explicit format, but no alignment, as in the case of `{val:X4}`. + - `void AppendFormatted(T t, int alignment, string format) where T : System.IFormattable`, which deals with interpolations having an explicit format and alignment, as in the case of `{val,4:X}`. +- Have a public method with the signature `override string ToString()`, which returns the built string. + +> *Note*: It is not a compile-time error to omit any of the `AppendFormatted` overloads, but if the handler is to be maximally robust, it should support all the formats recognized by the default handler. *end note* + +The new overload of `LogMessage` takes a custom handler instead of `string`, and retrieves the string as formatted by that handler. When such overloads exist, if a corresponding handler exists and the interpolated string expression is not a constant ([§12.8.3](expressions.md#1283-interpolated-string-expressions)), the compiler generates code to call the one taking a handler. In such cases, the compiler generates code to + +- call the handler constructor +- in lexical order within the interpolated string expression + - pass each interpolated string expression segment to `AppendLiteral` + - pass each interpolation to the appropriate `AppendFormatted` method. +- return the final string as the value of the interpolated string expression. +- execute the body of `LogMessage`. + +##### §custInterpStrExpInhibCustHandler Inhibiting a custom handler + +If a handler constructor has a final parameter of type `bool` that is an out parameter, when that constructor is called that parameter’s value is tested. If it is true, the behavior is as if that parameter were omitted. However, if it is false, the interpolated string expression is not processed further; that is, the handler is *inhibited*. Specifically, the interpolation expressions are not evaluated, and the methods `AppendLiteral` and `AppendFormatted` are not called. + +``` csharp +public LogInterpolatedStringHandler(int literalLength, int formattedCount, + out bool processString) +{ + if (some_condition) + { + processString = false; + return; + } + else + { + processString = true; + // continue construction + } +} +``` + +*Note*: The interpolations in an interpolated string expression may contain side effects (as result from `++`, `--`, assignment, and some method calls). If a handler is inhibited, none of the side effects in the interpolated string expression are evaluated. If a handler is not inhibited, all of the side effects in the interpolated string expression are evaluated. *end note* + +##### §custInterpStrExpPassInfo Passing information to/from a custom handler + +It can be useful to pass other information to, and receive information back from, the custom handler. This is done via the attribute `System.Runtime.CompilerServices.InterpolatedStringHandlerArgument`. Consider the following new overloads to the message logger program: + +```csharp +public class Logger +{ + // … + public void LogMessage(bool flag, int count, + [InterpolatedStringHandlerArgument("count","flag","")] + LogInterpolatedStringHandler builder) + { + // … + } +} + +public ref struct LogInterpolatedStringHandler +{ + // … + public LogInterpolatedStringHandler(int literalLength, int formattedCount, + int count, bool flag, Logger logger) + { + // … + } +} +``` + +Attribute `InterpolatedStringHandlerArgument` is applied to the handler parameter, which shall follow the declarations of the parameters that are to be passed to the handler. The attribute constructor argument shall be a comma-separated list of zero or more strings that name the parameters to be passed, along with their order. An empty string designates the instance from which the handler is being invoked. As such, the attribute constructor call above containing `"count","flag",""` requires a matching handler constructor. If the attribute constructor argument list is empty, the behavior is as if the attribute was omitted. + +If an `out bool` parameter is also declared to allow the handler to be inhibited (§custInterpStrExpInhibCustHandler) that parameter shall be the final one. + ## 23.6 Attributes for interoperation For interoperation with other languages, an indexer may be implemented using indexed properties. If no `IndexerName` attribute is present for an indexer, then the name `Item` is used by default. The `IndexerName` attribute enables a developer to override this default and specify a different name. From 8bd7898a051d9cc2632e4807322413021a209fdb Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 25 Jan 2026 14:54:18 -0500 Subject: [PATCH 5/7] fix formatting --- standard/attributes.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index a6eac3c37..c6bc6db72 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -1209,7 +1209,7 @@ val = { FF}; 2 * val = 510. In the call to `LogMessage`, the target of the interpolated string expression argument is parameter `msg`, which has type `string`. As such, according to [§12.8.3](expressions.md#1283-interpolated-string-expressions), the default interpolated string expression handler is invoked. The following subclause (§custInterpStrExpCustHandling) shows how to use a custom handler. -In order to provide custom processing to the program shown in §custInterpStrExpDefHandling, a *custom interpolated string expression handler* is needed. Here then is the message logger with a custom handler added (which although it does nothing more than behave like the default handler, it provides the hooks for customization): +In order to provide custom processing to the program above, a *custom interpolated string expression handler* is needed. Here then is the message logger with a custom handler added (which although it does nothing more than behave like the default handler, it provides the hooks for customization): ```csharp @@ -1274,7 +1274,7 @@ The output produced is, as follows: val = { FF}; 2 * val = 510. ``` -A type having the attribute `System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute` is said to be an *applicable interpolated string handler type*. +A type having the attribute `System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute` is said to be an *applicable interpolated string handler type*. To qualify as a custom interpolated string expression handler, a class or struct type shall have the following characteristics: @@ -1374,5 +1374,3 @@ For interoperation with other languages, an indexer may be implemented using ind > Now, the indexer’s name is `TheItem`. > > *end example* - - From 90087e77945ee5fddca526e818cde9e443ffac28 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 25 Jan 2026 14:58:02 -0500 Subject: [PATCH 6/7] fix formatting --- standard/expressions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/standard/expressions.md b/standard/expressions.md index 4499dadb5..cfeea00d4 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1510,6 +1510,7 @@ An *interpolated_string_expression* is classified as a value, which is evaluated 1. If the target of an assignment or method-call argument has type `string`, the expression is processed by the default interpolated string handler, `System.Runtime.CompilerServices.DefaultInterpolatedStringHandler`, and the result has type `string`. 1. If the target of an assignment or method-call argument has a custom interpolated string handler (§custInterpStrExpHandler) type, then + - If the interpolated string contains no interpolations, the expression is processed as if the target type was `string`. - Otherwise, the expression is processed by the custom interpolated string handler and the result has that custom interpolated string handler’s type. @@ -1522,7 +1523,7 @@ static void M(SomeInterpolatedStringHandler s) { } int val = 255; M($"no interpolations");// invokes M(string) M($"{val}"); // invokes M(SomeInterpolatedStringHandler) -string s1 = $"{val}"; // default handler used, as target has type string +string s1 = $"{val}"; // default handler used, as target has type string M(s1); // invokes M(string) SomeInterpolatedStringHandler str2 = $"{val}"; // custom handler used M(s2); // invokes M(SomeInterpolatedStringHandler) @@ -1595,6 +1596,7 @@ Then: *end example* A *constant interpolated string* is an *interpolated_string_expression* that contains + - no interpolations, or - interpolations whose *expression*s are constant expressions of type `string`, and these interpolations have no *interpolation_minimum_width*, *Regular_Interpolation_Format*, or *Verbatim_Interpolation_Format* specifiers. From 08391a40d1192a7638cd19a9427adb84f9968b98 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 25 Jan 2026 15:02:12 -0500 Subject: [PATCH 7/7] fix formatting --- standard/expressions.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index cfeea00d4..e3fabdd5e 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1511,8 +1511,8 @@ An *interpolated_string_expression* is classified as a value, which is evaluated 1. If the target of an assignment or method-call argument has type `string`, the expression is processed by the default interpolated string handler, `System.Runtime.CompilerServices.DefaultInterpolatedStringHandler`, and the result has type `string`. 1. If the target of an assignment or method-call argument has a custom interpolated string handler (§custInterpStrExpHandler) type, then - - If the interpolated string contains no interpolations, the expression is processed as if the target type was `string`. - - Otherwise, the expression is processed by the custom interpolated string handler and the result has that custom interpolated string handler’s type. +- If the interpolated string contains no interpolations, the expression is processed as if the target type was `string`. +- Otherwise, the expression is processed by the custom interpolated string handler and the result has that custom interpolated string handler’s type. These rules are illustrated by the following: @@ -1525,7 +1525,7 @@ M($"no interpolations");// invokes M(string) M($"{val}"); // invokes M(SomeInterpolatedStringHandler) string s1 = $"{val}"; // default handler used, as target has type string M(s1); // invokes M(string) -SomeInterpolatedStringHandler str2 = $"{val}"; // custom handler used +SomeInterpolatedStringHandler str2 = $"{val}"; // custom handler used M(s2); // invokes M(SomeInterpolatedStringHandler) ``` @@ -1597,8 +1597,8 @@ Then: A *constant interpolated string* is an *interpolated_string_expression* that contains - - no interpolations, or - - interpolations whose *expression*s are constant expressions of type `string`, and these interpolations have no *interpolation_minimum_width*, *Regular_Interpolation_Format*, or *Verbatim_Interpolation_Format* specifiers. +- no interpolations, or +- interpolations whose *expression*s are constant expressions of type `string`, and these interpolations have no *interpolation_minimum_width*, *Regular_Interpolation_Format*, or *Verbatim_Interpolation_Format* specifiers. For example, $"Hello", $"{cs1}, world!" (given `const string cs1 = $"Hello";`), $"{"Hello," + $"world!"}", and $"xxx{(true ? $"{"X"}" : $"{$"{"Y"}"}")}yyy" are all constant interpolated strings. However, $"{123}" and $"{"abc"}{123.45}" are not.