-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Why isn't the new System.FormattableString
a struct?
#518
Comments
The current design was intended to allow specialization of the factories and subclasses of |
It's really unfortunate that the current design always comes with the cost of an object allocation, just to minimize the API surface area. This is difficult to workaround with the current design. Developers would have to include their own custom implementation of With my proposal, you wouldn't have to include the factory at all because it would do overload resolution on the You could choose to only ship the non-generic But are we really that concerned about the added surface area of |
@justinvp The shape of the language under your proposal would be very strange. We would have to say that the allowable target types of a conversion from an interpolated string to an instance of the type FormattableString depends on the details of the interpolations... and the caller's code would have to change (that target type) when the interpolations change (i.e. added, deleted, change type, etc). I'd say no, that is not a price we'd be willing to pay in the shape of the language. I suggest you talk with @AlexGhiondea who drove the tradeoffs resulting in the shape of the API in its current form. |
/cc @jaredpar @nguerrera @ericstj CC'ing to those who signed off on the shape of the API; you may be interested in this discussion. |
It could be: public struct FormattableString<T1,T2,T3> // Up to 7?
{
public FormattableString(string messageFormat, (T1,T2,T3) args)
{
MessageFormat = messageFormat;
Args = args;
}
public string MessageFormat { get; }
public (T1, T2, T3) Args { get; }
} |
API was approved in 2014 and its already shipped in numerous frameworks, including .NET Desktop 4.6. I don't think you can change it compatibly. You could add a new API and have the compiler use that if present. |
So sad.. :) |
I'm not saying something can't be done. I'm just saying that compatibility has to be a design point in any changes made given that this is an already released API. |
Why isn't the new
System.FormattableString
type astruct
?It's currently a
class
. This means that formatting a string with the invariant culture using the new string interpolation language feature and the built-inFormattableString.Invariant
method results in an unnecessary allocation of theSystem.FormattableString
class.For example:
Compiles to:
The call to
System.Runtime.CompilerServices.FormattableStringFactory.Create
allocates a new instance of theFormattableString
class.(Not to mention the object[] array and boxing allocations; I'll get back to those.)
In the comments of a related issue (#506), I asked why
FormattableString
isn't astruct
.@gafter replied:
In such specialized subclasses, you would be able to avoid the array allocation, but in exchange you have to allocate a new
FormattableString
subclass.I would have liked to see something like the following:
With this,
FormattableString.Invariant
can be used without allocatingFormattableString
on the heap.The implementation of
System.Runtime.CompilerServices.FormattableStringFactory
becomes the following (which isn't all that useful now):Instead of relying on
System.Runtime.CompilerServices.FormattableStringFactory.Create
overloads to construct specialized instances, why not allow any type that implementsIFormattable
and do overload resolution on the type's constructors? Along the lines of how the collection initializer feature works (where a type must implementIEnumerable
and have a publicAdd
method), this would work with any type that implementsIFormattable
and has a constructor that takes astring format
argument followed by one or more arguments.To avoid the array and boxing allocations, just like we have
Action
,Action<T>
,Action<T1, T2>
,Action<T1, T2, T3>
, etc., there could be a few built-in generic struct types (e.g.FormattableString<T>
,FormattableString<T1, T2>
, andFormattableString<T1, T2, T3>
) along with similar generic overloads toString.Format
, as an optimization for common number of arguments.Such a design has other benefits.
For example, logging libraries such as NLog use the following pattern:
Usage:
The whole purpose of these methods is to avoid the array, boxing, and string.Format allocations when the
Info
log level isn't enabled, making the logging functionality "pay-for-play".With the existing design of
FormattableString
as a class, you couldn't really leverage the new string interpolation feature with allocation-conscious logging APIs like this.However, with the suggested design change, you would be able to implement this and avoid the allocations (the array and boxing allocations could be eliminated altogether):
Which would allow string interpolation feature to be used without sacrificing any of the optimizations to avoid allocations.
I really hope its not too late to consider a design tweak here, otherwise we have to live with
FormattableString
as a class in theSystem
namespace andFormattableString.Invariant
helper as-is forever.I'd be happy to help with any and all changes to roslyn and coreclr (implementation, tests, documentation, etc.) to make something like this happen.
/cc @gafter, @stephentoub, @KrzysztofCwalina, @terrajobst
The text was updated successfully, but these errors were encountered: