Releases: axuno/SmartFormat
v3.6.0
Release Notes
Thread Safety Enhancements
-
Parser
:- The parsing logic in
Parser
has been refactored by replacing stateful instance variables. - The
Parser.ParseFormat(...)
method is now thread-safe, ensuring safe operations in multi-threaded environments.
- The parsing logic in
-
SmartFormatter
:- All
SmartFormatter.Format...
methods are thread-safe. - Removed the
ThreadStatic
attribute from theSmart.Default
instance ofSmartFormatter
. - Added parallel unit tests to ensure thread-safe operations with shared
SmartFormatter
instances using differentSmart.Extensions
. - Updated documentation in
Parser
,Smart
, andSmartFormatter
classes to clarify the thread safety of methods.
- All
Also see further remarks regarding thread-safety in the Wiki
Heads Up
- Removal of
ThreadStatic
Attribute forSmart.Default
:- The
ThreadStatic
attribute for theSmart.Default
instance ofSmartFormatter
has been removed. - This change addresses user feedback regarding their lack of favor and the increased GC pressure it caused in multi-threaded environments like ASP.NET Core.
- The removal is intended to enhance usability and performance in multi-threaded environments. It may, however, break existing code.
- The
User Feedback
Sample Usage: Using One SmartFormatter
Instance with Several Threads in Parallel
The following example demonstrates how to use a single SmartFormatter
instance with multiple threads in parallel. This ensures thread-safe operations and efficient resource utilization.
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SmartFormat;
public class Program
{
public static void Main()
{
// Create a single instance of SmartFormatter
var smartFormatter = Smart.CreateDefaultSmartFormat();
// Concurrent dictionary to store results
var results = new ConcurrentDictionary<long, string>();
var options = new ParallelOptions { MaxDegreeOfParallelism = 100 };
// Run parallel tasks
Parallel.For(0L, 1000, options, i =>
{
// Re-use the same SmartFormatter instance, where the Format method is thread-safe.
// The static Smart.Format can be used as well, producing the same results
results.TryAdd(i, smartFormatter.Format("{0:D3}", i));
});
// Output results
var sortedResult = results.OrderBy(r => r.Value).ToList();
foreach (var result in sortedResult)
{
Console.WriteLine(result.Value);
}
}
}
What's Changed in Detail
- Refactor parsing for thread safety in #467
- Update appveyor api key for nuget in #468
- Shift CI Publishing Workflow to GitHub Actions in #469
- Move code quality CI to Github Action CodeQuality.yml in #470
- Make
SmartFormatter.Format(...)
methods thread-safe in #473 - Bump version to v3.6.0 in #474
Full Changelog: v3.5.3...v3.6.0
v3.5.3
What's Changed
- Add support for specifying separator between units when using TimeFormatter by @hakksor in #459
- Bump version to v3.5.3 in #463
Example:
TimeFormatter
allows for nested formats:
var ci = CultureInfo.GetCultureInfo("en");
// Using standard:
_ = Smart.Format(ci, "{1:time:", new TimeSpan(1,1,1,1,1));
// Output: "1 day 1 hour 1 minute 1 second"
// Using ListFormatter:
_ = Smart.Format(ci, "{1:time: {:list:|, | and }}", new TimeSpan(1,1,1,1,1));
// Output: "1 day, 1 hour, 1 minute and 1 second"
New Contributors
Full Changelog: v3.5.2...v3.5.3
v3.5.2
What's Changed
- Enhancement: Enable chaining from
StringBuilder.AppendSmart()
andStringBuilder.AppendLineSmart()
by @Klikini in #452 - [Fix Conditional Formatter] Use invariant decimal parsing by @karljj1 in #456
- Bump version to v3.5.2 in #457
Backstage Changes
- Update appveyor build and test scripts for cross-platform compatibility in #446
- Fix: Creation of AltCover report for Codecov in #447 and #455
- Update package versions in project files in #453
- Update NUnit v4.2.2 to v4.3.0 in #457
New Contributors
Full Changelog: v3.5.1...v3.5.2
v3.5.1
What's Changed
- Microsoft Security Advisory CVE-2024-43485 in #442
- Update dependencies in SmartFormat.Tests in #443
- Bump version to v3.5.1 in #444
Full Changelog: v3.5.0...v3.5.1
v3.5.0
What's Changed
Feature: Add interface IFormattingExtensionsToggle to skip formatting (#436)
- Added the
IFormattingExtensionsToggle
interface to allow skipping formatting byIFormatter
extensions. - This interface is primarily used by
ISource
extensions that receive it with theISelectorInfo
parameter. - By setting
IFormattingExtensionsToggle.DisableFormattingExtensions
totrue
, formatting can be skipped. This can be useful when theISource
found a value inISource.TryEvaluateSelector
where default formatting cannot reasonably be done.
Security: Update of System.Text.Json (#435)
- Bumped
System.Text.Json
to v8.0.4 - Fixed a vulnerability in .NET when calling the JsonSerializer.DeserializeAsyncEnumerable method against an untrusted input using System.Text.Json, which could result in Denial of Service.
Feature: Implement ISpanFormattable for DefaultFormatter (#434)
- Implemented
ISpanFormattable
forDefaultFormatter
ISpanFormattable
is 5% faster than IFormattable, with 24% less allocations// Performance test case Smart.FormatInto(output, null, _placeholder0005Format, 1234567.890123f, 1234567.890123f, 1234567.890123f, 1234567.890123f, 1234567.890123f);
BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3737/23H2/2023Update/SunValley3) 13th Gen Intel Core i7-13700K, 1 CPU, 24 logical and 16 physical cores .NET SDK 8.0.302 [Host] : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2 .NET 8.0 : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2 Job=.NET 8.0 Runtime=.NET 8.0 | Method | N | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio | |------------------------- |------ |--------------:|------------:|------------:|------:|--------:|-----------:|-------------:|------------:| | ISpanFormattable | 100 | 74.502 us | 0.3693 us | 0.3273 us | 8.52 | 0.05 | 4.0283 | 62.5 KB | 2.76 | | IFormattable | 100 | 77.927 us | 0.5760 us | 0.5388 us | 8.91 | 0.07 | 5.2490 | 82.0 KB | 3.62 |
Feature: ReflectionSource (#426)
- Made
ReflectionSource.TypeCache
static
for better performance. - If the
static
cache has undesired effects on your code logic, consider to disable the cache (ReflectionSource.IsTypeCacheEnabled = false
). - Implemented a mechanism to control the size of
ReflectionSource.TypeCache
and remove the oldest item first.
Enhancement: DictionarySource (#426)
- Dynamics in
DictionarySource
now use case-sensitivity setting - Cache for
IReadOnlyDictionary
now has instance scope
Refactor: Split off classes Registry and Evaluator from SmartFormatter (#424)
SmartFormatter
- Separated members for handling
ISource
andIFormatter
extensions into internal classRegistry
- Separated members for evaluating formats into internal class
Evaluator
- Existing members of
SmartFormatter
remain unchanged and are not yet marked as obsolete
ZCharArray
- Added a lightweight, thread-safe container that rents a buffer from an
ArrayPool<char>
and returns it when disposed - Simplifies passing around the buffer without intermediate memory allocations
ZCharArray
contains most frequently used methods for writing data into the underlying buffer- Used in
FormattingInfo
methods (see below) for low memory allocation
FormattingInfo
Added methods useful in custom IFormatter
s:
- public ZCharArray FormattingInfo.FormatAsSpan(IFormatProvider, Format, object?): Works like SmartFormatter.Format(...) for the specified Format
- public ZCharArray FormattingInfo.FormatAsSpan(IFormatProvider, Placeholder, object?): Gets the value for the
Placeholder
and applies itsFormat
- public bool TryGetValue(Placeholder, out object?): Tries to get the value for a
Placeholder
Evaluator
- Internal class that supplies the methods for evaluating
Placeholder
andFormat
objects - Contains the methods that
FormattingInfo
uses to evaluatePlaceholder
andFormat
objects - Has other methods formerly included in
SmartFormatter
that are now moved toEvaluator
- Includes a partial class for handling events in all steps of the evaluation process
Registry
- Internal class that contains all methods for handling
ISource
andIFormatter
extensions that have been moved fromSmartFormatter
Feature: Format.HasNested checks for existing Placeholder in Items (#416)
- Added
Format.HasNested
property that checks Items for existing Placeholder
Test: Add unit test for nested scope (#404)
- Added unit test for nested scope
Enhancement: Ensure Format instances get returned to object pool (#402)
- Ensured that Format instances get returned to object pool
Refactor: Internal SmartFormat.Pooling
classes (#401)
- Simplified pooling classes and made methods and names more consistent
Chore: Remove unused performance test projects (#398)
- Removed unused performance test projects
Fix: Returning a StringBuilder exceeding default capacity to StringBuilderPool (#397)
- Fixed an issue where returning a
StringBuilder
exceeding the default capacity toStringBuilderPool
would throw an exception.
Refactor: Remove code duplications in SmartFormatter (#396)
- Removed code duplications in SmartFormatter
- Unified preprocessor directives
Chore: Remove redundant DependsOnTargets from SmartFormat.csproj (#394)
- Removed redundant DependsOnTargets from SmartFormat.csproj
Full Changelog: v3.4.0...v3.5.0
v3.4.0
What's Changed
.NET Framework 4.6.2
SmartFormat has transitioned to using .NET Framework 4.6.2 (net462)
as its target framework, replacing the now unsupported .NET Framework 4.6.1 (net461)
. The support for net461
ended in 2022, while net462
will continue to receive support until January 2027.
Switching to a different patch version of a target framework is generally not considered a breaking change. This is referred to as an in-place update by Microsoft. In the case of SmartFormat, we have not encountered any runtime issues with the existing API after this transition.
The same holds true for the new netstandard2.0
reference to ZString.
.NET 6.0 and .NET 8.0
SmartFormat has expanded its compatibility by adding .NET 6.0 (net60)
and .NET 8.0 (net80)
as additional target frameworks. You can find details about their end of support here.
All Target Frameworks
We have removed the SmartFormat.ZString
assembly and replaced it with a reference to the ZString package. This change does not affect the API.
Commits in detail
- Fix: Update nuget api key in #378
- Target Framework Updates in #389 (merged the version/3.4.0 branch)
Thanks to @thompson-tomo for his first contribution with #377
Full Changelog: v3.3.2...v3.4.0
v3.3.2
Reasoning for the Update
- Regression caused by #368
- If there are namespace collisions with Cysharp.Text using v3.3.1 please update to v3.2.2
What's Changed
- Change all public types in namespace Cysharp.Text to internal in #372
- Stop namespace collisions with Cysharp.ZString nuget package
- Affects: class, struct, interface, delegate, enum
- using https://github.com/zzzprojects/findandreplace on command line, so we can automate this step:
"fnr.exe" --cl --dir "SmartFormat.ZString\repo\src\ZString" --fileMask ".cs,.tt" --includeSubDirectories --caseSensitive --useRegEx --find "public (?=.*(class |struct |enum |interface |delegate ))" --replace "internal " - Add a unit test to get an alert when Cysharp.Text objects are public (e.g. after an update)
- Bump version to v3.3.2
Full Changelog: v3.3.1...v3.3.2
v3.3.1
Fix
PluralRule for DualFromZeroToTwo: Now a value of 2 is covered and will not throw. Frend is one of the affected languages. Closes #369 in #370
Enhancement
Dictionary<string, PluralRuleDelegate> PluralRule.IsoLangToDelegate
holds delegate
s with the pluralization rule per language. Changing a value of this dictionary will change the pluralization rules globally. This is not recommended, but possible. After a change calling PluralRules.RestoreDefault()
will restore the default rules.
Other Changes
- Unit tests: Convert NUnit Classic Assert to Constraint Model in #364
- Bump NUnit package reference to v4.0.1 in #367
- Linq optimizations in #366
- Update package Cysharp.ZString to v2.5.1 in #368
- Bump version to v3.3.1 in #371
Full Changelog: v3.3.0...v3.3.1
v3.3.0
What's Changed
- Add support for nested formats in
LocalizationFormatter
by @zacateras in #350. This is useful, if the string to localize contains a SmartFormat placeholder instead of a pure text. Example: If the format is"{:L:{ProductType}}"
, theProductType
placeholder will be replaced with the variable content "pen". "pen" will in turn be localiced to "bic" for theFR
locale. DictionarySource
has an option to evaluateIReadOnlyDictionary<TKey,TValue>
sources by @axunonb in #353. To enable, setDictionarySource.IsIReadOnlyDictionarySupported
totrue
(default isfalse
). This is for types that only implementIReadOnlyDictionary<TKey,TValue>
, but notIDictionary
.- Bump version to v3.3.0 in #356
New Contributors
- Thanks to @zacateras for the first contribution in #350
Full Changelog: v3.2.2...v3.3.0
v3.2.2
What's Changed
Fix
-
PluralLocalizationFormatter
does not treat a numeric string as valid argument (resolves #345, restore behavior of v3.1.0 and before) in #346 -
Bump version to v3.2.2
Meaning:
Smart.Format("{0:{}|is null or empty}", "1234");
will not implicitly invokePluralLocalizationFormatter
but insteadConditionalFormatter
. So the result is "1234" as to be expected. A string argument toPluralLocalizationFormatter
is never accepted, even if it could be converted to a number.
Chore
- Update appveyor and github CI scripts
- Update change log for v3.2.1 in #331
- Update NuGet API Key in #335
- Update link to pluralization rules we use as a reference in #338
- Add unit test for DateOnly and TimeOnly types (NET6.0+) in #341
Full Changelog: v3.2.1...v3.2.2