Support C# 13 params collections (IEnumerable<T>, List<T>) - #478
Conversation
…ructs return null
nblumhardt
left a comment
There was a problem hiding this comment.
Looks good! Just one comment on diagnostics.
| { | ||
| return Activator.CreateInstance(paramType); | ||
| } | ||
| catch { } |
There was a problem hiding this comment.
In the catch block here, it might be worth pointing out:
which describes the cases in which Activator.CreateInstance is unlikely to succeed, and also the reflection-driven API we'd need to use in order to extend capability to them.
A Serilog.Debugging.SelfLog.WriteLine(...) call that includes the exception message and as much information as possible about the parameter being constructed would help users figure out why these might fail.
There was a problem hiding this comment.
Hello,
I updated the PR, here is what the log produces:
Unable to create an implicit instance of the params collection type 'Serilog.Settings.Configuration.Tests.BrokenCollection`1[System.String]' for parameter 'list' on method 'DummyBrokenCollection'.
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> System.InvalidOperationException: I am broken by design!
at Serilog.Settings.Configuration.Tests.BrokenCollection`1..ctor() in C:\Development\serilog-settings-configuration\test\Serilog.Settings.Configuration.Tests\DummyLoggerConfigurationExtensions.cs:line 72
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
--- End of inner exception stack trace ---
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
at Serilog.Settings.Configuration.ConfigurationReader.GetImplicitValueForNotSpecifiedKey(ParameterInfo parameter, MethodInfo methodToInvoke) in C:\Development\serilog-settings-configuration\src\Serilog.Settings.Configuration\Settings\Configuration\ConfigurationReader.cs:line 464
Let me know if this works for you.
nblumhardt
left a comment
There was a problem hiding this comment.
Thanks for the follow-up, just a couple of minor notes.
| var result = reader.GetImplicitValueForNotSpecifiedKey(param, method); | ||
|
|
||
| Assert.Null(result); | ||
| Assert.True(logs.Count > 0, "SelfLog count was 0. The catch block was never entered!"); |
There was a problem hiding this comment.
The cause of the assertion failing could be unrelated to the catch block. Better to just assert the condition and drop the diagnostic. Assert.NotEmpty(logs) should do the job.
| { | ||
| // Activator.CreateInstance is unlikely to succeed for collections lacking a parameterless constructor, | ||
| // or those relying on the [CollectionBuilder] attribute for initialization. | ||
| SelfLog.WriteLine($"Unable to create an implicit instance of the params collection type '{paramType}' for parameter '{parameter.Name}' on method '{methodToInvoke.Name}'.\n{ex}"); |
There was a problem hiding this comment.
SelfLog message styles are a bit inconsistent through the codebase, but the ones in Serilog generally use format strings, backticks or no delimiters around params, and colon before trailing exception:
SelfLog.WriteLine($"Unable to create an implicit instance of the params collection type `{0}` for parameter `{1}` on method `{2}`: {3}", paramType, parameter.Name, methodToInvoke.Name, ex);
SelfLog.WriteLine has no overload for accepting FormattableString or a builder, so the $ syntax will cause formatting to occur prematurely here.
There was a problem hiding this comment.
Thanks for the comments.
The signature or Selflog.WriteLine:
public static void WriteLine(string format, object? arg0 = null, object? arg1 = null, object? arg2 = null)
A) So I either collapse everything into that first format argument with $ or string.Format:
SelfLog.WriteLine($"Unable to create an implicit instance of the params collection type `{paramType}` for parameter `{parameter.Name}` on method `{methodToInvoke.Name}`: {ex}");
B) Or I extend the WriteLine function with a 4th optional arg3 = null parameter and do no $:
SelfLog.WriteLine("Unable to create an implicit instance of the params collection type `{0}` for parameter `{1}` on method `{2}`: {3}", paramType, parameter.Name, methodToInvoke.Name, ex);
In this serilog-settings-configuration repository option A is used everywhere.
In the serilog repository option B is used everywhere.
How do you want to move forward?
Let me know if I'm not seeing something here.
There was a problem hiding this comment.
Ah I see, thanks. Option A might be the way to go 👍
Might be worth us looking at adding explicit support to SelfLog for formattable strings, sometime down the track.
There was a problem hiding this comment.
Just out of curiosity about maintaining all these repos, when you say:
Might be worth us looking at adding explicit support to SelfLog for formattable strings, sometime down the track.
Do you track this anywhere, so when there is a major version update you noted down this should be implemented?
Hypotheticals:
Btw this would be a performance optimization, right? We only create the string when there is an active listener.
How do you do these migrations on breaking change you just completely remove this signature and just give a new one, if they want to upgrade they would have to change their Selflogs, or do you do a 2 phase migration, first declare it obsolete, provide a newer solution and next you remove that old signature?
There was a problem hiding this comment.
We only create the string when there is an active listener.
That's correct.
How do you do these migrations on breaking change
At this point, so many downstream sinks use those APIs that deprecation/removal isn't really a possibility. Anything we added would likely just be additive.
There probably isn't a huge pressing need to improve this, especially for this package where most code runs only once at start-up, and we only write those messages on an unlikely error path. Just mentally bookmarking it :-)
|
Thanks! 👍 |
Updated [Serilog](https://github.com/serilog/serilog) from 4.3.1 to 4.4.0. <details> <summary>Release notes</summary> _Sourced from [Serilog's releases](https://github.com/serilog/serilog/releases)._ ## 4.4.0 ## What's Changed * Emit SelfLog warning when extra arguments are provided by @matantsach in serilog/serilog#2222 * dont WriteQuotedJsonString for null by @SimonCropp in serilog/serilog#2216 * Pin System.Security.Cryptography.Xml to 8.0.3 in tests by @ArieGato in serilog/serilog#2232 * Route optional interfaces through OptionalInterfaceForwardingSink for restricted sinks by @ArieGato in serilog/serilog#2234 * `SelfMetrics` by @nblumhardt in serilog/serilog#2237 ## New Contributors * @matantsach made their first contribution in serilog/serilog#2222 * @ArieGato made their first contribution in serilog/serilog#2232 **Full Changelog**: serilog/serilog@v4.3.1...v4.4.0 Commits viewable in [compare view](serilog/serilog@v4.3.1...v4.4.0). </details> Updated [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration) from 10.0.0 to 10.0.1. <details> <summary>Release notes</summary> _Sourced from [Serilog.Settings.Configuration's releases](https://github.com/serilog/serilog-settings-configuration/releases)._ ## 10.0.1 ## What's Changed * Support LevelAlias names in configuration parsing by @mohammed-saalim in serilog/serilog-settings-configuration#465 * Fix: Update ConditionalSink expression syntax in sample app by @gyurebalint in serilog/serilog-settings-configuration#470 * issue-468: Fix empty/whitespace string converting to array type by @gyurebalint-CID in serilog/serilog-settings-configuration#469 * Add WriteTo.FallbackChain and WriteTo.Fallible support in configuration by @ArieGato in serilog/serilog-settings-configuration#474 * Fix/issue 441 by @gyurebalint in serilog/serilog-settings-configuration#471 * Support C# 13 params collections (IEnumerable<T>, List<T>) by @gyurebalint in serilog/serilog-settings-configuration#478 ## New Contributors * @mohammed-saalim made their first contribution in serilog/serilog-settings-configuration#465 * @gyurebalint made their first contribution in serilog/serilog-settings-configuration#470 * @gyurebalint-CID made their first contribution in serilog/serilog-settings-configuration#469 * @ArieGato made their first contribution in serilog/serilog-settings-configuration#474 **Full Changelog**: serilog/serilog-settings-configuration@v10.0.0...v10.0.1 Commits viewable in [compare view](serilog/serilog-settings-configuration@v10.0.0...v10.0.1). </details> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Resolves #476
Following up on the discussion in the issue, this PR implements support for C# 13 generic
paramscollections purely for the ergonomic and aesthetic benefits, while intentionally bypassingReadOnlySpanto avoid reflection complexity.Changes Included:
params IEnumerable<T>and safely satisfies the method contract by returning an empty array.params List<T>orHashSet<T>and usesActivator.CreateInstanceto generate an empty collection.ref structEvasion: Added a framework-agnostic check usingIsByRefLikeAttributeto catch types likeReadOnlySpan<T>. This ensures we don't attempt to pass aref structinto theMethodInfo.InvokeReflection pipeline, allowing it to safely fall back tonullacross all supported target frameworks.IEnumerable,List, andReadOnlySpan.