Last time, I explored various pathological code samples in which the [CallerLineNumber] attribute does not have obvious behavior. This time, I’ll cover the last of these new caller info attributes: [CallerMemberName].
The [CallerMemberName] attribute tells the compiler to insert the name of the containing member instead of a parameter’s default value. Unlike [CallerLineNumber] and [CallerFileName], this has no equivalent in C++; since the C / C++ versions of these features are in the preprocessor, they cannot be aware of member names.
Most calls to methods with optional parameters take place within a named method, so the behavior of this attribute is usually obvious. However, there are a couple of places where the exact method name is not so obvious.
If you call a [CallerMemberName] method inside a property or event accessor, what name should the compiler pass? Common sense indicates that it should pass the name of the property or event, since that’s the name you actually see in source code. That would also allow this attribute to be used for raising PropertyChanged events. However, this option doesn’t pass enough information, since it would not be possible to determine whether it was called from the getter or the setter. To expose the maximal amount of information, the compiler should pass the name of the actual method for the accessor –
I would assume that the compiler only passes the property name, since that is what most people would probably expect.
A less-trivial question arises when such a method is called from a constructor or static constructor. Should the compiler just pass the name of the class, since that’s what the member is named in source code? If so, there would be no way to distinguish between an instance constructor and a static constructor. Should the compiler pass the actual names of the CLR methods (
.cctor)? If so, there would be no way to tell the class name, which is worse. Should it pass both (
ClassName.ctor)? That would expose the maximal amount of information, but wouldn’t match the behavior in other members, which does not include the class name.
On a related note, what about calls to
this constructors that take [CallerMemberName] arguments? Is that considered part of the class’ constructor, even though the call is lexically scoped outside the constructor? If not, what should it pass?
A further related concern is field initializers. Since field initializers aren’t explicitly in any member, what should the compiler pass if you call a [CallerMemberName] method in a field initializer? I would assume that they’re treated like contructors (or static constructors for static field initializers)
I would assume that a call to a [CallerMemberName] method from within an anonymous method or LINQ query would use the name of the parent method.
The most interesting question concerns attributes. What should happen if you declare your own custom attribute that takes a [CallerMemberName] parameter, then apply the attribute somewhere without specifying the parameter?
If you place the attribute on a parameter, return value, or method, it would make sense for the compiler to pass the name of the method that the attribute was applied to. If you apply the attribute to a type, it might make sense to pass the name of that type. However, there is no obvious choice for attributes applied to a module or assembly.
I suspect that they instead chose to not pass these caller info in default parameters for attribute declarations, and to instead pass the parameters’ declared default values. If so, it would make sense to disallow caller info attributes in attribute constructor parameters. However, this would also prevent them from being used for attributes that are explicitly instantiated in normal code (eg, for global filters in MVC).
Next Time: Caller Info Attributes vs. Stack Walking
Republished from SLaks.Blog [36 clicks].
Read the original version here [2 clicks].