All notable changes to L.o.U.I.S. will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Structs
Louis.Text.TrueConditionInterpolatedStringHandler
andLouis.Text.FalseConditionInterpolatedStringHandler
are interpolated string handlers that only perform formatting if a given condition is true or false, respectively.
They can help implement, for example, conditional logging methods that take a condition and an interpolated string as a parameter, ensuring that string interpolation will only be performed if the result is actually used.
2.0.31 (2024-02-26)
- Class
Louis.ComponentModel.ParsableStringConverter<T>
and methodLouis.ComponentModel.SimpleStringConverter.AddToTypeDescriptor<T>
offer a ready-made type converter for any type implementingIParsable<TSelf>
.
ParsableStringConverter<T>
andAddToTypeDescriptor<T>
are only available on target platforms whereIParsable<TSelf>
is available, i.e. .NET 7 and later versions. - Two new boolean properties in class
Louis.Hosting.AsyncHostedService
let subclasses decide whetherStartAsync
should fail when the service is stopped before starting (FailOnSetupNotStarted
) orSetupAsync
completes withfalse
(FailOnSetupUnsuccessful
).
The default value istrue
for both properties. - Struct
Louis.Threading.InterlockedFlag
now implementsIEquatable<bool>
, as well as equality and inequality operators withbool
. - New struct
Louis.Threading.InterlockedReference<T>
encapsulates an object reference, so that it is always accessed in a thread-safe fashion. - New class
Louis.IO.ReadOnlyMemoryStream
implements a read-only, seekableStream
backed by aReadOnlyMemory<byte>
.
- BREAKING CHANGE: In class
Louis.Threading.AsyncService
, virtual methodSetupAsync
now returns aValueTask<bool>
instead of aValueTask
. If the result of the task isfalse
, the service is stopped and neitherExecuteAsync
norTeardownAsync
are called. - BREAKING CHANGE: In class
Louis.Threading.AsyncService
, the tasks returned from methodsWaitUntilStartedAsync
andStartAndWaitAsync
now have a result of typeAsyncServiceSetupResult
, with the following meaning:AsyncServiceSetupResult.Successful
means thatSetupAsync
completed with atrue
result;AsyncServiceSetupResult.NotStarted
means that the service was stopped before being started andSetupAsync
was therefore not called;AsyncServiceSetupResult.Unsuccessful
means thatSetupAsync
completed with afalse
result;AsyncServiceSetupResult.Canceled
means thatSetupAsync
was canceled;AsyncServiceSetupResult.Faulted
means thatSetupAsync
threw an exception.
- BREAKING CHANGE: In class
Louis.Threading.AsyncService
, methodsStartAsync
andStopAsync
have been renamed toStartAndWaitAsync
andStopAndWaitAsync
, respectively. The old names lead some users (and code analysis tools, e.g. ReSharper) to believe they were asynchronous versions ofStart
andStop
. - Class
Louis.Hosting.AsyncHostedService
now explicitly implements theStartAsync
andStopAsync
methods fromIHostedService
.
The two methods were previously only visible when casting an instance toIHostedService
, to avoid confusion with methods inherited fromLuois.Threading.AsyncService
. However, this violated design rule CA1033.
1.3.4 (2023-11-26)
- Class
Louis.ActionDisposable
implementsIDisposable
by invoking anAction
passed to its constructor. This can be useful, combined with C#'susing
statement, to ensure a piece of code gets executed at the end of a block or method, regardless of its result or outcome. Louis.LocalActionDisposable
has the same purpose and API ofActionDisposable
but, being aref struct
, it cannot be passed outside the method it is created in. On the other hand, it doesn't allocate space on the heap, taking just the size of a pointer on the stack, so it is preferrable toActionDisposable
in most cases, from both a performance and memory pressure point of view.- Class
Louis.AsyncActionDisposable
is similar toActionDisposable
, but it takes a possibly asynchronous delegate and uses it to implementIAsyncDisposable
as well asIDisposable
.
1.2.3 (2023-11-20)
- .NET 8.0 has been added as a target framework.
1.1.12 (2023-11-09)
- Class
Louis.ComponentModel.SimpleStringConverter<T>
provides a base class for type converters that can convert a specific type to and/or from a string. This abstract class takes care of boilerplate code and dealing withobject
s; subclasses only have to implement conversions betweenstring
s and strongly-typed instances. - Static method
Louis.ComponentModel.SimpleStringConverter.AddToTypeDescriptor<T, TConverter>
creates an instance ofTypeConverterAttribute
referencing a subclass ofSimpleStringConverter<T>
and registers it for use byTypeDescriptor.GetAttributes
. This enables a converter to be recognized by e.g.ConfigurationBinder
with just one line of clean, easy-to-understand code. - Classes
Louis.ComponentModel.MailAddressConverter
andLouis.ComponentModel.MailAddressCollectionConverter
perform conversion ofMailAddress
andMailAddressCollection
, respectively, to and fromstring
. They are also good examples of how to subclassSimpleStringConverter<T>
. - New fluent extension method
IfNotNullOrEmpty
invokes either anAction
or aFluentAction
if a string is neither null nor the empty string. - Fluent extension method
IfNotNull
now has overloads that work with nullable value types.
1.0.175 (2023-09-27)
First stable version. No actual changes since last preview.
1.0.173-preview (2023-09-26)
- Some methods in date- and time-related utilities, which took instances of
CultureInfo
orDateTimeFormatInfo
as parameters, did not check fornull
arguments. This has been fixed andArgumentNullException
is now thrown when needed.
1.0.170-preview (2023-09-26)
- #83 - Add date- and time-related utilities and extensions. The following types were added (links point to online API reference): TimeConstants, DateUtility, DateOnlyExtensions, DateTimeExtensions, and DateTimeUtility.
1.0.152-preview (2023-08-13)
Louis.Fluency.FluentExtensions.Switch
has new overloads that let you specify anIEqualityComparer<T>
interface to compare the given value with comparands associated with actions.- Pre-existing overloads of
Louis.Fluency.FluentExtensions.Switch
now useEqualityComparer<T>.Default
. As a consequance, the value and comparands no longer have to implementIEquatable<T>
. This change also makesFluentExtensions.Switch
usable withenum
s.
1.0.148-preview (2023-08-01)
- Added
Louis.Fluency.FluentExtensions.Chain
method group, semantically equivalent toInvoke
but using fluent methods or lambdas that return the same type as their first parameters. Now you can use even a local function as if it were an extension method.
1.0.138-preview (2023-07-23)
- Added some more logging hooks to
AsyncService
.AsyncHostedService
of course overrides all of them and logs appropriately. AsyncHostedService
now also logs when it is started / stopped by the host.
1.0.131-preview (2023-07-07)
- Added class
Louis.Threading.AsyncService
: a complete revamp of the oldAsyncWorker
class that was present in the very first alpha version of L.o.U.I.S., this class simplifies the implementation and use of long-running background tasks. - Added package
Louis.Hosting
with anAsyncHostedService
class, that extendsAsyncService
with logging and implements theIHostedService
interface for integration in ASP.NET applications, as well as any application based on thegeneric host
.
1.0.101-preview (2023-06-25)
- Added overloads of
Louis.Fluency.FluentExtensions.Invoke
that allow for additional arguments to be passed to lambdas to avoid the allocation of closure objects. - Added method
Louis.Fluency.FluentExtensions.InvokeIf
that calls the provided action only if a condition istrue
.
1.0.93-preview (2023-05-28)
- Added method
Louis.Fluency.FluentExtensions.IfNotNull
, which calls an action on an object and an additional argument only if the latter is notnull
. - Added overloads to
Louis.Fluency.FluentExtensions
that take simpleAction
s instead ofFluentAction
s as arguments.
1.0.83-preview (2023-04-25)
- Added method
Louis.Collections.EnumerableExtensions.WhereNot
, which works likeSystem.Linq.Enumerable.Where
but reverses the meaning of the predicate, returning only elements for which it returnsfalse
. - Added method
Louis.Collections.EnumerableExtensions.WhereNotNulLOrEmpty
, that filters out null and empty elements from sequences of strings. - Added method
Louis.Collections.EnumerableExtensions.WhereNotNulLOrWhiteSpace
, that filters out null, empty, and white-space-only elements from sequences of strings.
- Both overloads of
Louis.Collections.EnumerableExtensions.WhereNotNull
have been rewritten using local static functions instead of lambdas. Generated IL fornth ese methods is now smaller, slightly more performant, and makes no allocations.
- The overload of
Louis.Collections.EnumerableExtensions.WhereNotNull
that accepts sequences of nullable value types was not really an extension method (its first parameter had nothis
modifier).
1.0.73-preview (2023-03-12)
- Added class
PrefixingLogger
(in theLouis.Logging
package), that wraps an existingILogger
adding a given prefix to all log messages. Nice work by @ric15ni in PR #27.
1.0.59-preview (2023-03-10)
1.0.47-preview (2022-12-02)
- A rough capacity check has been added to all
StringBuilder
extension methods that append quoted strings. Computing the exact needed capacity, although possible, would totally kill performance; the added checks are a compromise that still helps, especially when appending long strings to string builders with no or little available buffer space.
1.0.39-preview (2022-11-26)
This version uses completely revamped build scripts and workflows.
As part of the transition to the new build system, versioning is now managed with Nerdbank.GitVersioning
, hence the versioning scheme change.
- .NET 7 has been added as a target platform.
- The new
DisposeSynchronously()
extension method for theIAsyncDisposable
interface lets you implementDispose()
in a class where you already have aDisposeAsync()
implementation. CallingDisposeAsync().GetAwaiter().GetResult()
is the same as callingDisposeSynchronously()
, but triggers warningCA2012
("UseValueTask correctly"); besides,DisposeSynchronously()
better conveys intent, making code more readable. - A new overload of
ValueTaskUtility.WhenAll()
takes a variable number ofValueTask
parameters.
- BREAKING CHANGE: Following .NET's Library support for older frameworks policy, support for .NET Core 3.1 has been removed.
- BREAKING CHANGE: The
Louis.Logging
namespace has been moved to its own library. Therefore,Louis.dll
no longer depends onMicrosoft.Extensions.Logging.Abstractions.dll
; the newLouis.Logging.dll
of course does. - The algorithm used by
ExceptionHelper.FormatObject
has changed as follows:- any exception thrown while trying to format an instance of
IFormattable
causes a fallback (previously, exceptions other thanFormatException
were not caught); - if formatting an
IFormattable
with an empty format causes an exception, the fallback action is now to treat the object as non-formattable (previously, the string<invalid_format>
was returned); - an exception thrown by
obj.ToString()
causes a string like<{objTypeName}:{exceptionTypeName}>
to be returned (previously, only the exception type name was specified in the returned string).
- any exception thrown while trying to format an instance of
- BREAKING CHANGE:
Louis.dll
no longer provides polyfills. Instead, it uses the PolyKit package. Projects that relied on polyfills provided by L.o.U.I.S. now should add aPackageReference
toPolyKit
. - BREAKING CHANGE: Class
Louis.Diagnostics.Throw
and the wholeLouis.ArgumentValidation
namespace have been removed. L.o.U.I.S. now relies on theCommunityToolkit.Diagnostics
package for throw helpers and argument validation. This change frees up development resources by eliminating the need to maintain features that, since the release of the .NET Community Toolkit 8.0, didn't add much value to begin with. - BREAKING CHANGE: All extension methods in
Louis.Text
that generate clipped string literals (StringExtensions.ToClippedLiteral
,StringBuilderExtensions.AppendClippedLiteral
, etc.) now throwArgumentOutOfRangeException
if theheadLength
and/ortailLength
parameter is a negative number. Previously, negative head / tail lengths were treated as 0. - BREAKING CHANGE: Methods
DisposingUtility.DisposeAll
andDisposingUtility.DisposeAllAsync
have been renamed toDispose
andDisposeAsync
respectively, so they are now overloads of the single-objectDispose
andDisposeAsync
.
- Passing incorrect parameter values to most public-facing methods could previously result in confusing exception messages. This has been fixed by implementing parameter checking in all public-facing methods instead of relying on dependency / runtime methods to fail.
- Calling
EnumerableExtensions.DisposeAll
orDisposingUtility.DisposeAll
from a UI thread could result in failures due to the loss of synchronization context. Tjhis has been fixed.
1.0.0-preview.8 (2022-09-13)
- Due to a logic bug, the
Louis.Text.Utf8Utility.GetMaxCharsInBytes
method returned incorrect results. This has been fixed.
1.0.0-preview.7 (2022-09-13)
- New extension methods for
string
,ReadOnlySpan<char>
, andStringBuilder
can convert a string (or span) to a C# literal while clipping long strings, leaving a head and/or a tail and an ellipsis. Especially useful for logging and exception messages. - Method
Louis.Diagnostics.ExceptionHelper.FormatObject
returns a text representation for an object, suitable for inclusion in an exception message. - Extension method
Louis.Diagnostics.StringBuilderExtensions.AppendFormattedObject
appends a text representation for an object, suitable for inclusion in an exception message, to the end of aStringBuilder
. - The
Louis.RangeCheck
class provides methods for easy in-range verification and clamping, with or without custom comparers. - The new
Validated
class provides methods for faster argument validation thanRequire
, when only a simple non-nullability check is needed. Methods ofValidated
do not initiate validation chains, but are faster and consume less stack than their namesakes inRequire
.
More importantly,Validated.NotNull
can be used when the type of the checked parameter is an open generic type with neither aclass
nor astruct
constraint.Require.NotNull
would not work in this case, because the compiler could not resolve the ambiguity between overloads. - New
Throw.Aggregate
andThrow.Aggregate<T>
helper methods for throwingAggregateException
s.
- BREAKING CHANGE: The
Arg
class (in namespaceLouis.ArgumentValidation
) has been renamed toRequire
to make its intent clearer, as e.g. inRequire.NotNull(str)
. - BREAKING CHANGE: The
Value
method in classRequire
(f.k.a.Arg
) has been renamed toOf
, as inRequire.Of(value).GreaterThanZero()
. - BREAKING CHANGE: The
ArgHelper
class has been completely revamped and now includes methods that create and throw exceptions, so that calling methods can remainthrow
-less and be better optimized and JITted. - BREAKING CHANGE: The
AsyncWorker
class has been retired from this project. It may reappear in a future version. - BREAKING CHANGE: The
ThreadSafeDisposable
class has been retired from this project, as it brought too little value to be of any actual use. - BREAKING CHANGE: The
ArgExtensions
class, containing methods to checkstring
arguments for conformance to URI / URL formats, has been retired. Its methods may reappear in a future version, probably in a different form. - All overloads of the
AppendQuotedClippedLiteral
,AppendVerbatimClippedLiteral
, andAppendClippedLiteral
methods (in classLouis.Text.StringBuilderExtensions
) have been split in two versions (with and without theuseUnicodeEllipsis
parameter) instead of having an optional parameter. This change minimizes the chance of binary incompatibilities with future versions of L.o.U.I.S.
1.0.0-preview.6 (2022-08-24)
- The
Louis.Logging
namespace containsILogger
extensions to log using interpolated strings instead of separated message formats and arguments. Not as fast asLoggerMessage
-created delegates, but still pretty fast. If the desired log level is not enabled on a logger, parameter evaluation and string interpolation don't happen at all; most importantly, you don't have to disrupt your flow to create a partial method every time you want to add a log write.
The new overloads have a single drawback: since their custom string interpolation handler uses thread-staticStringBuilder
s, you cannot useawait
in interpolation expressions. If you do, very bad things can and will happen, from garbled log messages to apparently random exceptions, including null pointer exceptions. - Also in
Louis.Logging
, moreILogger
extensions to log constant strings without turning them into zero-argument templates. Besides the small performance advantage, these extensions avoid putting constant log messages in the global log formatter cache.
1.0.0-preview.5 (2022-08-23)
Louis.Text.UnicodeCharacterUtility
is a slightly modified version of an internal class of the Roslyn compiler. Its methods provide the same functionality as SyntaxFacts.IsIdentifierStartCharacter, SyntaxFacts.IsIdentifierPartCharacter, and SyntaxFacts.IsValidIdentifier, plus an overload of the latter that takes a read-only span instead of a string.
1.0.0-preview.4 (2022-08-20)
- Type parameters
T1
andT2
inLouis.Fluency.FluentAction<T,T1>
andLouis.Fluency.FluentAction<T,T1,T2>
delegates are no longer contravariant.
Contravariance lead to the need for more verbose lambda syntax whenT1
and/orT2
was a value type. For example, given aStringBuilder builder
and abyte[] bytes
, to concatenate the hexadecimal representations of all bytes in the array you would now writebuilder.ForEach(bytes, (sb, b) => sb.Append(b.ToString("x2")))
, whereas in previous versions of Louis you had to write the same code asbuilder.ForEach(bytes, (StringBuilder sb, in byte b) => sb.Append(b.ToString("x2")))
.
1.0.0-preview.3 (2022-08-20)
- Two new overloads of
Louis.Fluency.FluentExtensions.ForEach
allow to iterate over spans instead of enumerables.
- XML documentation for generic methods of class
Louis.Diagnostics.Throw
lacked a description of their type parameter.
1.0.0-preview.2 (2022-08-17)
- Method
AsyncWorker.StartAsync
tried to start an already started Task, resulting in anInvalidOperationException
.
1.0.0-preview.1 (2022-08-15)
Initial release.