-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Champion "CallerArgumentExpression" #287
Comments
If expansion of the supported caller info attributes is going to be discussed in the LDM is there a chance that some quantity of #87 be mentioned as well? |
@HaloFour I think that would be reasonable. If we're going to do more in this space, we probably want to do it all at once. |
Related corefx proposal that would take advantage of this feature: dotnet/corefx#17068 |
Do I need to modify the proposal I'm working on to include his suggestions, or will they be reviewed just by looking at the issue? |
My intent is to bring up this one request to the design group. It sounds like @CyrusNajmabadi will bring up the others in that discussion. |
Related: #443 void M(object param, [CallerArgumentExpression(nameof(param))] string arg = null) {} |
@alrz I agree with the proposal, but it isn't strictly necessary for this to move forward. The end user will not have to touch this attribute. Only a handful of methods in the framework will use it, and for those it's fine to simply hard-code the string. |
Great to see that this has been discussed in the LDM, I wish I'd commented just 2 days later so I would have seen that 😄 I see this is the text for the
Does that mean I should open a proposal in the corefx repo? |
I opened a corefx proposal for this, see https://github.com/dotnet/corefx/issues/21809. |
Call me strange, but I still don't feel comfortable with optional arguments in public APIs. To the proposal in question, the callee shouldn't be required to know anything about the caller. And the caller should know what it was doing just by the call stack of the exception. Otherwise, you have worst problems in your hands and this is not the solution. |
Caller attributes are not a new concept so I think we should be past that argument by now. |
How would this work on extension methods? I'm thinking specifically of something like Shouldly here. |
@yaakov-h Oh wow, I hadn't even thought about this feature working in that way! I guess it'll do basically the same thing for extension methods-- capture everything before the dot that represents the expression is being invoked on. e.g. Off the top of my head: public static class FooExtensions {
public static void Bar(this Foo f, [CallerArgumentExpression("f")] string fExpression = null) {}
}
(this as Foo).Bar(); // fExpression = "(this as Foo)" I have no idea how Shouldly works (I just read the README and it seems like dark magic), but it should be able to use CAE to get the string before the dot instead of whatever they use. |
@jamesqo it's definitely dark magic: it walks the stack, interprets the PDBs, then interprets the source code file. 😄 Thanks for the spec update, looks reasonable to me. 👍 |
Debug.Assert can't take advantage of this feature, because there is already an overload that takes an string, public static void Assert(bool condition);
public static void Assert(bool condition, string message); If you make I didn't come up with a solution to light up CallerArgumentExpression without losing source compatibility. |
@alrz We can maintain source compatibility. However, we may have to break binary compatibility by removing the first overload. |
Maybe you could do it in a library you use but that's simply not an option for Debug.Assert. |
There could be an helper type that takes all the values in the constructor as optional arguments: public static void Assert(AssertContext context, bool condition);
public static void Assert(AssertContext context, bool condition, string message); |
Maybe we could do this with a static extension method in "extention everything" feature, namespace System.Diagnostics {
public extension class DebugExtensions for Debug {
public new static void Assert(bool condition,
[CallerArgumetExpression("condition")] string message = default) { .. }
}
} If we were able to define To make it opt-in we could move the extension to another namespace like |
@alrz, are you expecting the extension method to win over the class method? |
Proposal: dotnet/csharplang#287 Test plan: #52745 Addresses part of LDM notes: https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-06-14.md
Are there plans to provide a shim package for .NET Standard? |
Not AFAIK. |
I prefer internal attribute class declarations over shim packages, myself. |
simpler version, without unnecessary clutter
where |
@vbcodec this feature is already done. |
@vbcodec That signature would be misleading, since the actually emitted method must have an additional string parameter. There's no other general mechanism for obtaining the string value than having the string value passed via parameter. (Ultimately, the compiler must emit something expressible in past runtimes.) |
It is matter of compiler. Compiler can generate such additional parameters, or use thread-based ambient context (must be added to NET 6 BCL), to transfer these strings |
It can't. Source code doesn't exist at runtime.
There's no thread based ambient context that contains the original source text to transfer. |
@vbcodec is suggesting either of two things, both of which are technically possible but both of which are problematic IMO:
|
Thanks for expanding my statement. My two corrections:
|
For future visitors: https://docs.microsoft.com/dotnet/api/system.runtime.compilerservices.callerargumentexpressionattribute Since .NET Standard is dead, sadly this attribute will never appear there. |
@IanKemp This actually doesn't matter. .NET Standard projects can do this: #if !NETCOREAPP3_0_OR_GREATER // This line is only needed if you are multitargeting with netcoreapp3.0+
namespace System.Runtime.CompilerServices;
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
internal sealed class CallerArgumentExpressionAttribute : Attribute
{
public CallerArgumentExpressionAttribute(string parameterName)
{
ParameterName = parameterName;
}
public string ParameterName { get; }
}
#endif This general approach works for all compiler-recognized attributes. I first used it with CallerMemberNameAttribute, and SerializableAttribute was another example of this before it was added to .NET Standard. It's very important to declare these attributes as internal so that consuming projects don't end up seeing conflicting definitions of System.Runtime.CompilerServices.CallerArgumentExpressionAttribute. Declaring the attribute as internal does not restrict where it can be used within the project that declared it. |
That is no help in gaining the actual functionality of the attribute, which means it has no purpose if you are targeting .Net Standard. |
@NetMage That's incorrect. The actual functionality of the attribute is in full effect so long as you are using the .NET 6 SDK. The same thing has been true of all compiler-recognized attributes; the target framework doesn't affect compiler behavior so long as somebody declares the correct types. You can demonstrate for yourself that this works when targeting .NET Standard. Here's one way: Setup (click to expand)<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<LangVersion>10</LangVersion>
</PropertyGroup>
</Project> Program.csusing System;
using System.Runtime.CompilerServices;
WriteExpression(1 + 2);
static void WriteExpression(object p, [CallerArgumentExpression("p")] string expression = null)
{
Console.WriteLine(expression);
} System.Runtime.CompilerServices.CallerArgumentExpressionAttribute.cs#if !NETCOREAPP3_0_OR_GREATER // This line is only needed if you are multitargeting with netcoreapp3.0+
namespace System.Runtime.CompilerServices;
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
internal sealed class CallerArgumentExpressionAttribute : Attribute
{
public CallerArgumentExpressionAttribute(string parameterName)
{
ParameterName = parameterName;
}
public string ParameterName { get; }
}
#endif Result: You can verify the same thing by going a longer route involving a This works within the same assembly no matter what the assembly's target framework is, and it works across the boundary between any two assemblies no matter what each target framework is. |
I can verify this works with .NET 4.7.2, .NET Core 3.1, and .NET Standard 2.0 given the example here. I've already updated my Thanks! 🎉 |
Why is this issue still open? Something missing? |
ECMA Spec is missing |
We keep issues open until they are documented in the ECMA specification over at dotnet/csharpstandard. |
That's now 3 years later, is the ECMA spec that far behind, or was this simply forgotten? Where should this go? I don't mind giving it a go, as I'm looking at implementing this for F# (see fsharp/fslang-suggestions#966). It looks like the latest ECMA version is from 2023, but this isn't in. I assume it should go into section 22.5? |
The 2023 ECMA is for C# 7. Yes, it’s the far behind. Progress is being made at @dotnet/csharplang. They’re working on C# 8 ATM. As this is for C# 10, it’s gonna be a bit before this is closed. |
Allow developers to capture the expressions passed to a method, to enable better error messages in diagnostic/testing APIs and reduce keystrokes.
See #205
The text was updated successfully, but these errors were encountered: