Why do the C# Caller Info Attributes need a default value?
Asked Answered
K

4

14

I just came across the C# 5 Caller Info Attributes (http://msdn.microsoft.com/en-us/library/hh534540.aspx).

This seems like a very useful feature, and I've read up some documentation (http://www.codeproject.com/Tips/606379/Caller-Info-Attributes-in-Csharp).

However, I'm just wondering: Why does one have to pass in default values? How are they used?

The following example code shows how one would use the Caller Info Attributes:

public static void ShowCallerInfo([CallerMemberName] 
  string callerName = null, [CallerFilePath] string 
  callerFilePath = null, [CallerLineNumber] int callerLine=-1)
{
    Console.WriteLine("Caller Name: {0}", callerName);
    Console.WriteLine("Caller FilePath: {0}", callerFilePath);
    Console.WriteLine("Caller Line number: {0}", callerLine);
}

My question is: What are the default values of null, null, and -1 used for? How is the code above different from:

public static void ShowCallerInfo([CallerMemberName] 
  string callerName = "hello", [CallerFilePath] string 
  callerFilePath = "world", [CallerLineNumber] int callerLine=-42)
{
    Console.WriteLine("Caller Name: {0}", callerName);
    Console.WriteLine("Caller FilePath: {0}", callerFilePath);
    Console.WriteLine("Caller Line number: {0}", callerLine);
}

The way I understand it, these are optional parameters, and the compiler supplies the default value, replacing whatever default value we assign. In that case, why are we specifying the default values? Is there some weird edge case where the compiler may not be able to fill in the values, and resorts to the defaults we had supplied? If not, then why are we being asked to enter this data? It seems rather clumsy to ask devs to supply defaults which won't ever be used.

Disclaimer: I tried googling this but I wasn't able to find anything. I'm almost afraid of asking questions on SO because most such newbie questions are met with such hostility, but as a last resort I'm going to hazard a question. Moderators/senior users, no offense intended - I really did try and find information elsewhere before posting this.

Klenk answered 23/6, 2014 at 3:25 Comment(1)
Please don't mistake blunt "what did you try?" replies with hostility. Brevity is encouraged, however cold it may seem. The newbiness of a question can be judged by the number of google hits you get. If you cant find it on google with a decent range of queries, it probably isn't a trivial question. This question is not trivial and i enjoyed reading the answers.Agrippina
S
1

There are a few uses mentioned in other answers which all seem valid.

Something they have missed was that these essentially tells the compiler to rewrite the calls to these functions with static values. But these values are not always available. In those cases, the compiler will not rewrite the calls, so the default values will be used.

Examples:

  1. If you compile a dll with a function that has these attributes, expose that to an in memory generated script, (say via Roslyn), that code may not have a "filename".

    • One may argue that the generated script should then invoke the method with argument values provided instead, but that means the same code that the compiler can compile statically (i.e csc mycodefile.cs) wont work with dynamic compilation at runtime even with the same context which would be confusing.
  2. You can also invoke this method via reflection, which the compiler simply isnt aware of to add these values.

    • Runtime/BCL could be built to force the reflection caller to provide these values, but there isn't any meaningful values for filename and line number in that context anyway.
  3. You can also add [CallerMemberName] to an attribute constructor and apply that attribute on a class. This will not have a member name.

See Member Names in Docs

Attribute constructor

The name of the method or property to which the attribute is applied. If the attribute is any element within a member (such as a parameter, a return value, or a generic type parameter), this result is the name of the member that's associated with that element.

No containing member (for example, assembly-level or attributes that are applied to types)

The default value of the optional parameter.

You can also provide the values explicitly, if you want to hide the caller information. for some reason. (May be if you use code obfuscation, these values may not affected, so you may want to provide these values in those cases to hide the caller).

See Remarks in Docs

Caller Info values are emitted as literals into the Intermediate Language (IL) at compile time. Unlike the results of the StackTrace property for exceptions, the results aren't affected by obfuscation.

You can explicitly supply the optional arguments to control the caller information or to hide caller information.

Strung answered 20/11, 2019 at 12:48 Comment(1)
This makes more sense to me than the other answers written above. Thanks!Klenk
Y
9

Those parameters need a default value because the Caller Info attributes were implemented using optional parameters and optional parameters require a default value. That way the call can be simply ShowCallerInfo() without having to send any parameters and the compiler will add the relevant ones.

Why was it implemented using optional parameters to begin with is a deeper question. They could have made it without, and the compiler would need to "inject" those parameters before actual compilation started, but as opposed to optional parameters (which is a C# 4.0 feature) it would not be backward compatible and it will break other compilers/code analysis tools.

Yahiya answered 23/6, 2014 at 10:40 Comment(5)
Am I right in understanding that the "developers" who develop C# (master devs?) went about implementing this feature in a bit of a hacky way for reasons to do with backward compatibility etc? Wouldn't older versions of the c# compiler not support the new attributes (and therefore still give errors?) How does this help with backwards compatibility?Klenk
@Klenk If you would use an older compiler that supports optional parameters the code could compile because there's a default value. You would just print the default values constantly. I wouldn't call it a hack, more relying on existing features.Yahiya
So you mean to say that older compilers would ignore the [CallerMemberName] (and other two) attributes, even though they don't know what those attributes do?Klenk
@Klenk yeah, why not? You can create any attribute you want and apply it to parameters/methods/etc. There's no reason the compiler would mind. However those classes are a part of .net 4.5 so when using an older framework you need to implement them as stubs.Yahiya
Ok, that makes sense then - one would still have to implement the attributes as stubs. I'm not sure if this was the best way for our Master Devs to implement this feature but I suppose it's certainly easier to declare a few Attribute stubs rather than removing all attributes from code.Klenk
T
4

They need the defaults so that the parameters can be flagged as optional. If you don't specify the parameters when calling the method, the compiler will inject the correct values for you, but only if you didn't specify them. If you do, then the "magic" of those attributes won't happen.

From my understanding, these attributes do not affect runtime and are purely for compile time so the defaults are only to make sure the parameters are optional.

Therapsid answered 23/6, 2014 at 3:30 Comment(0)
G
2

To put it in another way, on the callee (the method called where the attribute is applied to the parameter) the parameter must exist. On the other hand, the caller must pass those arguments and the only way for the compiler to allow for an unspecified argument is to give it a default value.

Although attributes may influence code generation or runtime execution, the source must be valid if one removes all the attributes. Therefor the default value must be defined on the callee and the compiler just generate the argument value based on the applied attribute instead of the current default value defined on the callee.

Gourley answered 23/6, 2014 at 15:9 Comment(0)
S
1

There are a few uses mentioned in other answers which all seem valid.

Something they have missed was that these essentially tells the compiler to rewrite the calls to these functions with static values. But these values are not always available. In those cases, the compiler will not rewrite the calls, so the default values will be used.

Examples:

  1. If you compile a dll with a function that has these attributes, expose that to an in memory generated script, (say via Roslyn), that code may not have a "filename".

    • One may argue that the generated script should then invoke the method with argument values provided instead, but that means the same code that the compiler can compile statically (i.e csc mycodefile.cs) wont work with dynamic compilation at runtime even with the same context which would be confusing.
  2. You can also invoke this method via reflection, which the compiler simply isnt aware of to add these values.

    • Runtime/BCL could be built to force the reflection caller to provide these values, but there isn't any meaningful values for filename and line number in that context anyway.
  3. You can also add [CallerMemberName] to an attribute constructor and apply that attribute on a class. This will not have a member name.

See Member Names in Docs

Attribute constructor

The name of the method or property to which the attribute is applied. If the attribute is any element within a member (such as a parameter, a return value, or a generic type parameter), this result is the name of the member that's associated with that element.

No containing member (for example, assembly-level or attributes that are applied to types)

The default value of the optional parameter.

You can also provide the values explicitly, if you want to hide the caller information. for some reason. (May be if you use code obfuscation, these values may not affected, so you may want to provide these values in those cases to hide the caller).

See Remarks in Docs

Caller Info values are emitted as literals into the Intermediate Language (IL) at compile time. Unlike the results of the StackTrace property for exceptions, the results aren't affected by obfuscation.

You can explicitly supply the optional arguments to control the caller information or to hide caller information.

Strung answered 20/11, 2019 at 12:48 Comment(1)
This makes more sense to me than the other answers written above. Thanks!Klenk

© 2022 - 2024 — McMap. All rights reserved.