Proposal: Expand supported Caller Info Attributes · dotnet/csharplang · Discussion #87 (original) (raw)
Feb 14, 2017
· 63 comments· 20 replies
Copied from dotnet/roslyn#351
Problem:
The currently supported caller info attributes can only provide the name of the method, the name of the source file and the line number of the call in the source file. This is fine for scenarios such as simplifying implementations of INotifyPropertyChanged
. However, if using these attributes for logging the amount of information available is quite limited.
Solution:
Expand the number of supported caller info attributes to allow embedding additional diagnostic information. The following list is quite expansive to discuss/argue over the potential possibilities.
CallerColumnNumberAttribute: The column number of where the method is invoked.
CallerTypeNameAttribute: The simple name of the declaring type of the calling method.
CallerNamespaceAttribute: The namespace of the declaring type of the calling method.
CallerFullTypeNameAttribute: The full name of the declaring type of the calling method.
CallerTypeAttribute: The declaring type of the calling method. This is replaced by the compiler by ldtoken
of the type followed by a call to Type.GetTypeFromHandle
.
CallerMethodAttribute: The MethodBase of the calling method. This is replaced by the compiler by ldtoken
of the method reference followed by a call to MethodBase.GetMethodFromHandle
.
Example usage:
using System; using System.Diagnostics; using System.Runtime.CompilerServices;
namespace Project1 { public static class Program { private static void Foo([CallerMemberName] string memberName = null, [CallerTypeName] string typeName = null, [CallerNamespaceName] string namespaceName = null, [CallerFullTypeName] string fullTypeName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = 0, [CallerColumnNumber] int columnNumber = 0, [CallerType] Type type, [CallerMethod] MethodBase method) { Debug.Assert(memberName == "Main"); Debug.Assert(typeName == "Program"); Debug.Assert(namespaceName == "Project1"); Debug.Assert(fullTypeName == "Project1.Program"); Debug.Assert(filePath == "c:\foo\bar\Program.cs"); Debug.Assert(lineNumber == 29); Debug.Assert(columnNumber == 12); Debug.Assert(type == typeof(Program)); Debug.Assert(method == typeof(Program).GetMethod("Main")); }
public static void Main() {
Foo();
}
}
}
It was also suggested by @AdamSpeight2008 that a caller-info struct be supported for situations where you want several of these values but don't want to have to declare/annotate multiple parameters.
Class CallerInfo Public ReadOnly MemberName As String Public ReadOnly TypeName As String Public ReadOnly Namespace As String Public ReadOnly FullTypeName As String Public ReadOnly FilePath As String Public ReadOnly LineNumber As Integer Public ReadOnly ColumnNumber As Integer Public ReadOnly Type As Type Public ReadOnly Method As MethodBase End Class
Public Sub Foo( ByVal info As CallerInfo = Nothing) End Sub
LDM history:
You must be logged in to vote
I'm not sure I see a need for CallerColumnNumberAttribute
. What's the use case you see for that one?
By supporting just CallerTypeAttribute
, wouldn't we gain access to the same information as what would be in CallerTypeNameAttribute
, CallerNamespaceAttribute
, and CallerFullTypeNameAttribute
?
CallerTypeNameAttribute
is available from Type.Name, CallerNamespaceAttribute
is available from Type.Namespace and CallerFullTypeNameAttribute
is available through Type.FullName.
I do like the idea of having a CallerInfo
struct that can be used in place of having to specify each individual attribute. This would be an "all or nothing" scenario in the sense that if I use the struct, I get all of the values filled in. If I don't want/need all of them, I can still specify them individually.
You must be logged in to vote
0 replies
Would it be possible to make the value lazy, so they are produced when need / used.
You must be logged in to vote
0 replies
@AdamSpeight2008 I'm not sure that would work. From MSDN, "Caller Info values are emitted as literals into the Intermediate Language (IL) at compile time." That would prevent them from being lazily evaluated since they aren't evaluated at run time.
You must be logged in to vote
0 replies
I'm not sure I see a need for
CallerColumnNumberAttribute
. What's the use case you see for that one?
Completes the set, mostly. I could see some use for capturing the position of multiple calls to the same method nested in some other expression, but that might be pretty esoteric.
By supporting just
CallerTypeAttribute
, wouldn't we gain access to the same information as what would be inCallerTypeNameAttribute
,CallerNamespaceAttribute
, andCallerFullTypeNameAttribute
?
Yes. C# already supports CallerTypeNameAttribute
, so I built this proposal from there.
My use cases would be covered if the only caller-info attributes were CallerMethod
, CallerFilePath
and CallerLineNumber
. Everything else of value to me can be derived from that.
I'm not sure that would work. From MSDN, "Caller Info values are emitted as literals into the Intermediate Language (IL) at compile time." That would prevent them from being lazily evaluated since they aren't evaluated at run time.
Technically true, but it's possible that some of the work could be deferred until runtime. Specifically with CallerInfo
where you accept a struct
of the values. That struct
could have a constructor that only accepts the RuntimeMethodHandle
for the calling method pushed by ldtoken
, the file name and the file line number. Then the actual method and type properties could be lazily evaluated by taking that RuntimeMethodHandle
and loading the actual MethodBase
and from that deriving the declaring Type
.
You must be logged in to vote
0 replies
C# already supports CallerTypeNameAttribute
Do you mean CallerMemberNameAttribute
?
So, right now we already have CallerFilePathAttribute
, CallerMemberNameAttribute
, and CallerLineNumberAttribute
.
I think if we just added CallerTypeAttribute
and CallerMemberAttribute
(which return a Type
and a MemberInfo
, respectively), would that work? I changed to returning MemberInfo
as it's the base class for MethodBase
and I think would allow CallerMember
to be more generally useful and work for properties as well.
Technically true, but it's possible that some of the work could be deferred until runtime.
I would think this wouldn't be desirable due to performance considerations. It also introduces different compile-time behavior between using the attributes directly and using the struct. Then again, it might not be possible to implement CallerTypeAttribute
and CallerMemberAttribute
at compile time, not sure about that.
You must be logged in to vote
0 replies
Do you mean
CallerMemberNameAttribute
?
Oops, you're right. None of the attributes cover the type at the moment.
Then again, it might not be possible to implement
CallerTypeAttribute
andCallerMemberAttribute
at compile time, not sure about that.
I don't see why not. They're not as simple as expressions as pushing a string literal or an integer literal, but they're still relatively easy. It's two IL opcodes to derive the Type
or the MethodBase
and push it onto the stack, ldtoken
with the type or method token followed by either call Type.GetTypeFromHandle
or call MethodBase.GetMethodFromHandle
. The former is literally how typeof(T)
works. The tokens themselves are deterministic at compile-time.
You must be logged in to vote
0 replies
@HaloFour typeof(T)
is a runtime call though, not a compile time literal. Are you saying that the compiler would simply emit the required IL opcodes instead of emitting the literal? I guess the difference is that by emitting the literal value, there is no runtime cost to getting the information (other than just normal parameter access overhead) but there would be slight runtime cost associated with accessing CallerTypeAttribute
and CallerMemberAttribute
. Granted, I doubt it's significant enough of an overhead to make much difference, especially since these probably wouldn't have a high amount of access and something could probably be done to cache the call site information, similar to what's done with dynamic
.
You must be logged in to vote
0 replies
Yes, it would be a runtime call. The only way to avoid that would be to have those caller-info attributes applied to parameters of type RuntimeTypeHandle
or RuntimeMethodHandle
and have the compiler only emit ldtoken
which is still evaluated at runtime but should be faster. But all you're doing there is offloading the inevitable to the callee which is going to have to invoke those methods to get something useful anyway.
Either way, this is a lot less overhead than what people are doing now to support getting this kind of information which is accepting a MethodBase
and requiring the caller to call MethodBase.GetCurrentMethod
manually, or by getting a stack trace in the callee and trying to find the calling method, assuming that the JIT isn't inlining it.
You must be logged in to vote
0 replies
Either way, this is a lot less overhead than what people are doing now to support getting this kind of information
Absolutely agree on that! :) Yes, this introduces a small amount of overhead, but significantly less than what's incurred now to get the same information.
You must be logged in to vote
0 replies
Another possibility is to do something similar to what we do with strings literals. Have a embedded dictionary lookup table.
You must be logged in to vote
0 replies
Building general purpose computing with CallerMemberName is extremely frustrating due to the lack of CallerTypeName, leads to all kinds of voodoo required to coerce types from somewhere that you can locate who someone is.
You must be logged in to vote
0 replies
@scottdorman [CallerType]
and [CallerMember]
would be exactly what I want.
You must be logged in to vote
0 replies
@dotnetchris same here. Even just the fully qualified caller type name as a parameter attribute would solve a multitude of problems.
You must be logged in to vote
0 replies
[CallerMember]
would be my most preferred but I would completely be able to live with only [CallerTypeName]
You must be logged in to vote
0 replies
Basically I want more and more tools to be able to write programming to write my programming.
You must be logged in to vote
0 replies
This might be a whole other can of worms, but I think it would be extremely helpful if CallerMemberType could be used in generics as well. For example, you could have some function like this:
T MyFunc<[CallerMemberType] T>()
{
//do stuff
}
Then you could use it like so:
public MyClass MyProperty
{
get => MyFunc();
}
You must be logged in to vote
0 replies
Any information about CallerTypeName in .Net 6?
You must be logged in to vote
1 reply
This is a 2017 request. So I'd say check back in 2027,
You must be logged in to vote
0 replies
This really need some traction, as I keep coming back to needing this functionality when dealing with things like logging, or I'm working on "plugin-esque" applications.
I keep ending up creating messy "hacks" to get the information I need to meet some specs, and if this proposal was implemented I probalbly would have saved tens, if not hundreds of hours of development and maintainence of convoluted reflection -based logic
You must be logged in to vote
5 replies
FWIW, this post isn't super helpful. I cannot glean from it what in particular you need, and verify that adding it will be sufficient for you to "meet some specs".
It would be great to give crisp examples of what you're trying to do, how you do it currently, and which attributes in particular would make a substantive difference for you.
Thanks!
We need [CallerMemberTypeName]
that's the bare minimum.
Optimally we get [CallerMember]
and [CallerMemberType]
As stated earlier in this discussion:
My use cases would be covered if the only caller-info attributes were CallerMethod, CallerFilePath and CallerLineNumber. Everything else of value to me can be derived from that.
Two are already covered, the missing one would be CallerMethod
. That could return a MethodBase
or a RuntimeMethodHandle
. I believe every other value above could be derived from that, except for CallerColumnNumber, but that one might not be of value.
@CyrusNajmabadi I aggree with @dotnetchris that [CallerMemberTypeName]
is the bare minimum requirement, but only if the following two points are true:
- It returns the
FullName
of the type. - It never returns the name of a compiler generated type.
If any of those points can't be fulfilled we need [CallerMember]
(provides a MethodBase
or RuntimeMethodHandle
) or [CallerMemberType]
(provides a Type
) to be able to get the "real" full name ourselves.
This comment was marked as off-topic.
Maybe if we do it ourselves and raise a PR with awesome docs, tests and performance numbers. Do you have the free-time and skills for it?
Maybe if we do it ourselves and raise a PR with awesome docs, tests and performance numbers. Do you have the free-time and skills for it?
I feel like multiple sources have been submitted for this over the past half decade
You must be logged in to vote
0 replies
we are interested in CallerTypeNameAttribute too. Would be very nice for logging performancetracing etc
thank you
You must be logged in to vote
0 replies
We are also interested in the CallerTypeName attribute, specifically for logging needs. Would be nice to know what the current state of this is
You must be logged in to vote
2 replies
Related to call type name, it would be nice if that was smart enough to return either this.GetType or
Typeof(TSelf) (for static classes)
Would be nice to know what the current state of this is
Nothing has changed since hte last LDM meeting on the topic. If anything changes we will update this discussion. :)
I like the idea of a CallerInfo class, it would make debugging universally easier because it is not restricted to one specific aspect like the other Caller attributes
You must be logged in to vote
0 replies
CallerTypeName or CallerType would make things so much easier
You must be logged in to vote
0 replies
Lets keep this up.CallerTypeName
You must be logged in to vote
2 replies
I would like just a plain old caller type.
Agreed, type would be more useful, you can get the name from the type easily enough (and decide whether you want fully qualified or short, include info about generic type params or not, etc.)
Turns out I too need [CallerTypeName]
(context: a helper used in async tests). Parsing it out from [CallerFilePath]
should be good enough™️ in my case, but isn't guaranteed to be correct, since you can have >1 type in a file. In async methods it's impossible to get the correct StackFrame from a StackTrace. So my only options are to rely on CallerFilePath, specify filenames manually or have unpleasant nameof
s sprinkled around.
You must be logged in to vote
0 replies
only CallerTypeName
needed for me, any news plz?
You must be logged in to vote
0 replies
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
Please keep this discussion on topic. Discussions about other features, and your particular desire around them aren't appropriate to the discussion of the design here. Thanks.
Sometimes there are some special classes which need to do some initialization and no other class should do that, for those cases CallerMemberType
would really be useful, as we have the nameof
attribute already (for which refactoring works nicely) it could really be as simple as a string:
public class InitializedTypeGetter { private InitializedType? _init;
public void Init(InitializedType init, [CallerMemberType] string? caller = null)
{
if (_init != null)
{
throw new InvalidOperationException("Already initialized");
}
if (caller != nameof(MySpecialInitializator))
{
throw new InvalidOperationException($"Can only be initialized by {nameof(MySpecialInitializator)}");
}
_init = init;
}
public InitializedType Init=> _init ?? throw new InvalidOperationException("Not yet initialized");
}
You must be logged in to vote
4 replies
That feels… wrong. A type shouldn’t care who’s using it. If you want initialization to only occur from one place, don’t expose that initialization logic.
I don't aggree, there is a way to do exactly this already, you just need to put the InitializedTypeGetter
and the MySpecialInitializator
together in a new project (assembly) and then make the Init
function internal
and you get the desired functinality, which is perfectly acceptable. But that just isn't always practical and also having too many projects extends the build time.
Again: an “Init” function shouldn’t care who calls it. If the parameters are ok, then they’re ok.
@DomenPigeon Couldn't you just use Generics? Something like:
public void Init() { _init = typeof(T); // Could also use typeof(T).Name etc. if (_init != typeof(MySpecialInitializator) && !_init.IsSubclassOf(typeof(MySpecialInitializator))) throw new InvalidOperationException($"Can only be initialized by {nameof(MySpecialInitializator)}"); } // Procedure //
You could always use Generic constraints too:
public void Init() where T : MySpecialInitializator { } // Procedure //
This discussion was converted from issue #87 on September 08, 2020 19:36.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
- Heading
- Bold
- Italic
- Quote
- Code
- Link
- Numbered list
- Unordered list
- Task list
- Attach files
- Mention
- Reference
Select a reply
Loading
Uh oh!
There was an error while loading. Please reload this page.
👍 1 reacted with thumbs up emoji 👎 1 reacted with thumbs down emoji 😄 1 reacted with laugh emoji 🎉 1 reacted with hooray emoji 😕 1 reacted with confused emoji ❤️ 1 reacted with heart emoji 🚀 1 reacted with rocket emoji 👀 1 reacted with eyes emoji