Caller Info Attributes vs. Stack Walking

People sometimes wonder why C# 5 needs to add caller info attributes, when this information is already available by using the StackTrace class.  In reality, caller info attributes behave rather differently from the StackTrace class, for a number of reasons.

Advantages to Caller Info Attributes

The primary reason to use caller info attributes is that they’re much faster.  Stack walking is one of the slowest internal (as opposed to network IO) things you can do in .Net (disclaimer: I haven’t measured).  By contrast, caller info attributes have exactly 0 performance penalty.  Caller info is resolved at compile-time; the callsite is compiled to pass a string or int literal that was determined by the compiler.  Incidentally, this is why C# 5 doesn’t have [CallerType] or [CallerMemberInfo] attributes; the compiler team wasn’t happy with the performance of the result IL and didn’t have time to implement proper caching to make it faster.

Caller info attributes are also more reliable than stack walking.  Stack walking will give incorrect results if the JITter decides to inline either your method or its caller.  Since caller info attributes are resolved at compile-time, they are immune to inlining.  Also, stack walking can only give line number and filename info if the calling assembly’s PDB file is present at runtime, whereas [CallerFileName] and [CallerLineNumber] will always work. 

Caller info attributes are also unaffected by compiler transformations.  If you do a stack walk from inside a lambda expression, iterator, or async method, you’ll see the compiler-generated method; there is no good way to retrieve the name of the original method.  Since caller info attributes are processed by the compiler, [CallerMemberName] should correctly pass the name of the containing method (although I cannot verify this).  Note that stack walking will retrieve the correct line number even inside lambda expressions (assuming there is a PDB file)

Advantages To Stack Walking

There are still a couple of reasons to use the StackTrace class.  Obviously, if you want to see more than one frame (if you want your caller’s caller), you need to use StackTrace.  Also, if you want to support clients in languages that don’t feature caller info attributes, such as C# 4 or F#, you should walk the stack instead.

Stack walking is the only way to find the caller’s type or assembly (or a MemberInfo object), since caller info attributes do not provide this information.

Stack walking is also the only choice for security-related scenarios.  It should be obvious that caller info attributes can trivially be spoofed; nothing prevents the caller from passing arbitrary values as the optional parameter.  Stack walking, by contrast, happens within the CLR and cannot be spoofed. 

Note that there are some other concerns with stack walking for security purposes.

  • Filename and line number information can be spoofed by providing a modified PDB file
  • Class and function names are meaningless; your enemy can name his function whatever he wants to.  All you should rely on is the assembly name.
  • There are various ways for an attacker to cause your code to be called indirectly (eg, DynamicInvoking a delegate) so that his assembly isn’t the direct caller.  In fact, if the attacker invokes your delegate on a UI thread (by calling BeginInvoke), his assembly won’t show up on the callstack at all.  The attacker can also compile a dynamic assembly that calls your function and call into that assembly.
  • As always, beware of inlining

Subtleties of C# 5’s new [CallerMemberName]

UPDATE: Now that the Visual Studio 11 beta has shipped with this feature implemented, I wrote a separate blog post exploring how it actually behaves in these corner cases.

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 – get_Name or set_Name

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 (.ctor and .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 base or 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

Subtleties of C# 5’s new [CallerLineNumber]

UPDATE: Now that the Visual Studio 11 beta has shipped with this feature implemented, I wrote a separate blog post exploring how it actually behaves in these corner cases.

This is part 2 in a series about C# 5’s new caller info attributes; see the introduction.

The [CallerLineNumber] attribute tells the compiler to use the line number of the call site instead of the parameter’s default value.  This attribute has more corner cases than [CallerFileName].  In particular, unlike the C preprocessor’s __LINE__ macro, the C# compiler inserts the line number of a parsed method call.  Therefore, it is not always clear which line a method call expression maps to.

What should this call print:

static class Utils {
    int GetLine([CallerLineNumber] int line = 0) {
        return line;
    }
}

Console.WriteLine(
    Utils
        .
        GetLine
        (
        )
    );

Should it print the line number that the statement started? The line in which the call to GetLine started? The line containing the parentheses for GetLine? What if it’s in a multi-line lambda expression?

There are also a few cases in which methods are called implicitly by the compiler without appearing in source code.  What should this code print?

class Funny {
    public Funny Select(Func<object, object> d,
                        [CallerLineNumber]int line = 0) {
        Console.WriteLine(line + ": " + d(d));
        return this;
    }
}

var r = (from x in new Funny() 
         let y = 1 
         select 2);

This code contains two implicit calls to the Select method that don’t have a clear source line (it gets worse for more complicated LINQ queries)

In fact, it is possible to have an implicit method call with no corresponding source code at all.

Consider this code:

class Loggable {
    public Loggable([CallerLineNumber] int line = 0) { }
}
class SomeClass : Loggable { }

The compiler will implicitly generate a constructor for SomeClass that calls the base Loggable constructor with its default parameter value.  What line should it pass? In fact, if SomeClass is a partial class that is defined in multiple files, it isn’t even clear what [CallerFileName] should pass.

Also, what should happen in the unlikely case that a [CallerLineNumber] method is called on line 3 billion (which would overflow an int)? (This would be easier to test on Roslyn with a fake stream source) Should it give an integer overflow compile-time error?  If [CallerLineNumber] also supports byte and short parameters, this scenario will be more likely to happen in practice.

Next time: [CallerMemberName]

Subtleties of the new Caller Info Attributes in C# 5

UPDATE: Now that the Visual Studio 11 beta has shipped with this feature implemented, I wrote a separate blog post exploring how it actually behaves in these corner cases.

C# 5 is all about asynchronous programming.  However, in additional to the new async features, the C# team managed to slip in a much simpler feature: Caller Info Attributes.

Since C#’s inception, developers have asked for __LINE__ and __FILE__ macros like those in C and C++.  Since C# intentionally does not support macros, these requests have not been answered.  Until now.

C# 5 adds these features using attributes and optional parameters.  As Anders Hejlsberg presented at //Build/, you can write

public static void Log(string message,
    [CallerFilePath] string file = "",
    [CallerLineNumber] int line = 0,
    [CallerMemberName] string member = "")
{
    Console.WriteLine("{0}:{1} – {2}: {3}", 
                      file, line, member, message);

}

If this method is called in C# 5 without specifying the optional parameters, the compiler will insert the file name, line number, and containing member name instead of the default values.  If it’s called in older languages that don’t support optional parameters, those languages will pass the default values, like any other optional method.

These features look trivial at first glance.  However, like most features, they are actually more complicated to design in a solid and robust fashion.  Here are some of the less obvious issues that the C# team needed to deal with when creating this feature:

Disclaimer: I am basing these posts entirely on  logical deduction.  I do not have access to a specification or implementation of this feature; all I know is what Anders announced in his //Build/ presentation.  However, the C# team would have needed to somehow deal with each of thee issues.  Since these attributes are not yet supported by any public CTP, I can’t test my assumptions

To start with, they needed to create new compiler errors if the attribute is applied to an incorrectly-typed parameter, or a non-optional parameter.  Creating compiler errors is expensive; they need to be documented, tested, and localized into every language supported by C#.

The attributes should perhaps support nullable types, or parameter types with custom implicit conversions to int or string.  (especially long or short)

If a method with these attributes is called in an expression tree literal (a lambda expression converted to an Expression<TDelegate>), the compiler would need to insert ConstantExpressions with the actual line number or other info to pass as the parameter.

In addition to being supported in methods, the attributes should also be supported on parameters  for delegate types, allowing you to write

delegate void LinePrinter([CallerLineNumber] int line = 0);

LinePrinter printer = Console.WriteLine;
printer();

Each of the individual attributes also has subtle issues.

[CallerFilePath] seems fairly simple.  Any function call must happen in source code, in a source file that has a path.  However, it needs to take into account #line directives, so that, for example, it will work as expected in Razor views.  I don’t know what it does inside #line hidden, in which there isn’t a source file.

Next time: [CallerLineNumber]