Chapter 7. C# 5 bonus features

This chapter covers

  • Changes to variable capture in foreach loops
  • Caller information attributes

If C# had been designed with book authors in mind, this chapter wouldn’t exist, or it’d be a more standard length. I could claim that I wanted to include a very short chapter as a sort of palette cleanser after the dish of asynchrony served by C# 5 and before the sweetness of C# 6, but the reality is that two more changes in C# 5 that need to be covered wouldn’t fit into the async chapters. The first of these isn’t so much a feature as a correction to an earlier mistake in the language design.

7.1. Capturing variables in foreach loops

Before C# 5, foreach loops were described in the language specification as if each loop declared a single iteration variable, which was read-only within the original code but received a different value for each iteration of the loop. For example, in C# 3 a foreach loop over a List<string> like this

foreach (string name in names)
{
    Console.WriteLine(name);
}

would be broadly equivalent to this:

string name;                                  1
using (var iterator = names.GetEnumerator())  2
{
    while (iterator.MoveNext())
    {
        name = iterator.Current;              3
        Console.WriteLine(name);              4
    }
}

  • 1 Declaration of single iteration variable
  • 2 Invisible iterator variable
  • 3 Assigns new value to iteration variable on each iteration
  • 4 Original body of the foreach loop
Note

The specification has a lot of other details around possible conversions of both the collection and the elements, but they’re not relevant to this change. Additionally, the scope of the iteration variable is only the scope of the loop; you can imagine adding an extra pair of curly braces around the whole code.

In C# 1, this was fine, but it started causing problems way back in C# 2 when anonymous methods were introduced. That was the first time that a variable could be captured, changing its lifetime significantly. A variable is captured when it’s used in an anonymous function, and the compiler has to do work behind the scenes to make its use feel natural. Although anonymous methods in C# 2 were useful, my impression is that it was C# 3, with its lambda expressions and LINQ, that really encouraged developers to use delegates more widely.

What’s the problem with our earlier expansion of the foreach loop using just a single iteration variable? If that iteration variable is captured in an anonymous function for a delegate, then whenever the delegate is invoked, the delegate will use the current value of that single variable. The following listing shows a concrete example.

Listing 7.1. Capturing the iteration variable in a foreach loop
List<string> names = new List<string> { "x", "y", "z" };
var actions = new List<Action>();
foreach (string name in names)                    1
{
    actions.Add(() => Console.WriteLine(name));   2
}
foreach (Action action in actions)                3
{                                                 3
    action();                                     3
}                                                 3

  • 1 Iterates over the list of names
  • 2 Creates a delegate that captures name
  • 3 Executes all the delegates

What would you have expected that to print out if I hadn’t been drawing your attention to the problem? Most developers would expect it to print x, then y, then z. That’s the useful behavior. In reality, with a C# compiler before version 5, it would’ve printed z three times, which is really not helpful.

As of C# 5, the specification for the foreach loop has been changed so that a new variable is introduced in each iteration of the loop. The exact same code in C# 5 and later produces the expected result of x, y, z.

Note that this change affects only foreach loops. If you were to use a regular for loop instead, you’d still capture only a single variable. The following listing is the same as listing 7.1, other than the changes shown in bold.

Listing 7.2. Capturing the iteration variable in a for loop
List<string> names = new List<string> { "x", "y", "z" };
var actions = new List<Action>();
for (int i = 0; i < names.Count; i++)                1
{
    actions.Add(() => Console.WriteLine(names[i]));  2
}
foreach (Action action in actions)                   3
{                                                    3
    action();                                        3
}                                                    3

  • 1 Iterates over the list of names
  • 2 Creates a delegate that captures names and i
  • 3 Executes all the delegates

This doesn’t print the last name three times; it fails with an ArgumentOutOfRangeException, because by the time you start executing the delegates, the value of i is 3.

This isn’t an oversight on the part of the C# design team. It’s just that when a for loop initializer declares a local variable, it does so once for the whole duration of the loop. The syntax of the loop makes that model easy to see, whereas the syntax of foreach encourages a mental model of one variable per iteration. On to our final feature of C# 5: caller information attributes.

7.2. Caller information attributes

Some features are general, such as lambda expressions, implicitly typed local variables, generics, and the like. Others are more specific: LINQ is meant to be about querying data of some form or other, even though it’s aimed to generalize over many data sources. The final C# 5 feature is extremely targeted: there are two significant use cases (one obvious, one slightly less so), and I don’t expect it to be used much outside those situations.

7.2.1. Basic behavior

.NET 4.5 introduced three new attributes:

  • CallerFilePathAttribute
  • CallerLineNumberAttribute
  • CallerMemberNameAttribute

These are all in the System.Runtime.CompilerServices namespace. Just as with other attributes, when you apply any of these, you can omit the Attribute suffix. Because that’s the most common way of using attributes, I’ll abbreviate the names appropriately for the rest of the book.

All three attributes can be applied only to parameters, and they’re useful only when they’re applied to optional parameters with appropriate types. The idea is simple: if the call site doesn’t provide the argument, the compiler will use the current file, line number, or member name to fill in the argument instead of taking the normal default value. If the caller does supply an argument, the compiler will leave it alone.

Note

The parameter types are almost always int or string in normal usage. They can be other types where appropriate conversions are available. See the specification for details if you’re interested, but I’d be surprised if you ever needed to know.

The following listing is an example of all three attributes and a mixture of compiler-specified and user-specified values.

Listing 7.3. Basic demonstration of caller member attributes
static void ShowInfo(
    [CallerFilePath] string file = null,
    [CallerLineNumber] int line = 0,
    [CallerMemberName] string member = null)
{
    Console.WriteLine("{0}:{1} - {2}", file, line, member);
}

static void Main()
{
    ShowInfo();                                  1
    ShowInfo("LiesAndDamnedLies.java", -10);     2
}

  • 1 Compiler provides all three arguments from context
  • 2 Compiler provides only the member name from context

The output of listing 7.3 on my machine is as follows:

C:UsersjonProjectsCSharpInDepthChapter07CallerInfoDemo.cs:20 - Main
LiesAndDamnedLies.java:-10 - Main

You wouldn’t usually give a fake value for any of these arguments, but it’s useful to be able to pass the value explicitly, particularly if you want to log the current method’s caller using the same attributes.

The member name works for all members normally in the obvious way. The default values for the attributes are usually irrelevant, but we’ll come back to some interesting corner cases in section 7.2.4. First, we’ll look at the two common use cases I mentioned earlier. The most universal of these is logging.

7.2.2. Logging

The most obvious case in which caller information is useful is when writing to a log file. Previously when logging, you’d usually construct a stack trace (using System .Diagnostics.StackTrace, for example) to find out where the log call came from. This is typically hidden from view in logging frameworks, but it’s still there—and ugly. It’s potentially an issue in terms of performance, and it’s brittle in the face of JIT compiler inlining.

It’s easy to see how a logging framework can use the new feature to allow caller-only information to be logged cheaply, even preserving line numbers and member names in the face of a build that had debug information stripped and even after obfuscation. This doesn’t help when you want to log a full stack trace, of course, but it doesn’t take away your ability to do that, either.

Based on a quick sampling performed at the end of 2017, it appears that this functionality hasn’t been used particularly widely yet.[1] In particular, I see no sign of it being used in the ILogger interface commonly used in ASP.NET Core. But it’d be entirely reasonable to write your own extension methods for ILogger that use these attributes and create an appropriate state object to be logged.

1

NLog was the only logging framework I found with direct support and then only conditionally based on the target framework.

It’s not particularly uncommon for projects to include their own primitive logging frameworks, which could also be amenable to the use of these attributes. A project-specific logging framework is less likely to need to worry about targeting frameworks that don’t include the attributes, too.

Note

The lack of an efficient system-level logging framework is a thorny issue. This is particularly true for class library developers who wish to provide logging facilities but don’t want to add third-party dependencies and don’t know which logging frameworks their users will be targeting.

Whereas the logging use case needs specific thought on the part of frameworks, our second use case is a lot simpler to integrate.

7.2.3. Simplifying INotifyPropertyChanged implementations

The less obvious use of just one of these attributes, [CallerMemberName], may be obvious to you if you happen to implement INotifyPropertyChanged frequently. If you’re not familiar with the INotifyPropertyChanged interface, it’s commonly used for thick client applications (as opposed to web applications) to allow a user interface to respond to a change in model or view model. It’s in the System.ComponentModel namespace, so it’s not tied to any particular UI technology. It’s used in Windows Forms, WPF, and Xamarin Forms, for example. The interface is simple; it’s a single event of type PropertyChangedEventHandler. This is a delegate type with the following signature:

public delegate void PropertyChangedEventHandler(
    Object sender, PropertyChangedEventArgs e)

PropertyChangedEventArgs, in turn, has a single constructor:

public PropertyChangedEventArgs(string propertyName)

A typical implementation of INotifyPropertyChanged before C# 5 might look something like the following listing.

Listing 7.4. Implementing INotifyPropertyChanged the old way
class OldPropertyNotifier : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private int firstValue;
    public int FirstValue
    {
        get { return firstValue; }
        set
        {
            if (value != firstValue)
            {
                firstValue = value;
                NotifyPropertyChanged("FirstValue");
            }
        }
    }

    // (Other properties with the same pattern)

    private void NotifyPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

The purpose of the helper method is to avoid having to put the nullity check in each property. You could easily make it an extension method to avoid repeating it on each implementation.

This isn’t just long-winded (which hasn’t changed); it’s also brittle. The problem is that the name of the property (FirstValue) is specified as a string literal, and if you refactor the property name to something else, you could easily forget to change the string literal. If you’re lucky, your tools and tests will help you spot the mistake, but it’s still ugly. You’ll see in chapter 9 that the nameof operator introduced in C# 6 would make this code more refactoring friendly, but it’d still be prone to copy-and-paste errors.

With caller info attributes, the majority of the code stays the same, but you can make the compiler fill in the property name by using CallerMemberName in the helper method, as shown in the following listing.

Listing 7.5. Using caller information to implement INotifyPropertyChanged
if (value != firstValue)      1
{                             1
    firstValue = value;       1
    NotifyPropertyChanged();  1
}                             1

void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
                              2
}

  • 1 Changes within the property setter
  • 2 Same method body as before

I’ve shown only the sections of the code that have changed; it’s that simple. Now when you change the name of the property, the compiler will use the new name instead. It’s not an earth-shattering improvement, but it’s nicer nonetheless.

Unlike logging, this pattern has been embraced by model-view-viewmodel (MVVM) frameworks that provide base classes for view models and models. For example, in Xamarin Forms, the BindableObject class has an OnPropertyChanged method using CallerMemberName. Similarly, the Caliburn Micro MVVM framework has a PropertyChangedBase class with a NotifyOfPropertyChange method. That’s all you’re likely to need to know about caller information attributes, but a few interesting oddities exist, particularly with the caller member name.

7.2.4. Corner cases of caller information attributes

In almost all cases, it’s obvious which value the compiler should provide for caller information attributes. It’s interesting to look at places where it’s not obvious, though. I should emphasize that this is mostly a matter of curiosity and a spotlight on language design choices rather than on issues that will affect regular development. First, a little restriction.

Dynamically invoked members

In many ways, the infrastructure around dynamic typing tries hard to apply the same rules at execution time as the regular compiler would at compile time. But caller information isn’t preserved for this purpose. If the member being invoked includes an optional parameter with a caller information attribute but the invocation doesn’t include a corresponding argument, the default value specified in the parameter is used as if the attribute weren’t present.

Aside from anything else, the compiler would have to embed all the line-number information for every dynamically invoked member just in case it was required, thereby increasing the resulting assembly size for no benefit in 99.9% of cases. Then there’s the extra analysis required at execution time to check whether the caller information was required, which would potentially disrupt caching, too. I suspect that if the C# design team had considered this to be a common and important scenario, they’d have found a way to make it work, but I also think it’s entirely reasonable that they decided there were more valuable features to spend their time on. You just need to be aware of the behavior and accept it, basically. Workarounds exist in some cases, though.

If you’re passing a method argument that happens to be dynamic but you don’t need it to be, you can cast to the appropriate type instead. At that point, the method invocation will be a regular one without any dynamic typing involved.[2] If you really need the dynamic behavior but you know that the member you’re invoking uses caller information attributes, you can explicitly call a helper method that uses the caller information attribute to return the value. It’s a little ugly, but this is a corner case anyway. The following listing shows the problem and both workarounds.

2

The call will have the additional benefits of compile-time checking that the member exists and improved execution-time efficiency, too.

Listing 7.6. Caller information attributes and dynamic typing
static void ShowLine(string message,                1
    [CallerLineNumber] int line = 0)                1
{                                                   1
    Console.WriteLine("{0}: {1}", line, message);   1
}                                                   1

static int GetLineNumber(                           2
    [CallerLineNumber] int line = 0)                2
{                                                   2
    return line;                                    2
}                                                   2

static void Main()
{
    dynamic message = "Some message";
    ShowLine(message);                              3
    ShowLine((string) message);                     4
    ShowLine(message, GetLineNumber());             5
}

  • 1 Method you’re trying to call that uses the line number
  • 2 Helper method for workaround 2
  • 3 Simple dynamic call; line will be reported as 0.
  • 4 Workaround 1: cast the value to remove dynamic typing.
  • 5 Workaround 2: explicitly provide the line number using a helper method.

Listing 7.6 prints a line number of 0 for the first call but the correct line number for both workarounds. It’s a trade-off between having simple code and retaining more information. Neither of these workarounds is appropriate when you need to use dynamic overload resolution, and some overloads need caller information and some don’t, of course. As limitations go, that’s pretty reasonable in my view. Next, let’s think about unusual names.

Non-obvious member names

When the caller member name is provided by the compiler and that caller is a method, the name is obvious: it’s the name of the method. Not everything is a method, though. Here are some cases to consider:

  • Calls from an instance constructor
  • Calls from a static constructor
  • Calls from a finalizer
  • Calls from an operator
  • Calls as part of a field, event, or property initializer[3]

    3

    Initializers for automatically implemented properties were introduced in C# 6. See section 8.2.2 for details, but if you take a guess at what this means, you’re likely to be right.

  • Calls from an indexer

The first four of these are specified to be implementation dependent; it’s up to the compiler to decide how to treat them. The fifth (initializers) isn’t specified at all, and the final one (indexers) is specified to use the name Item unless IndexerNameAttribute has been applied to the indexer.

The Roslyn compiler uses the names that are present in the IL for the first four: .ctor, .cctor, Finalize, and operator names such as op_Addition. For initializers, it uses the name of the field, event, or property being initialized.

The downloadable code contains a complete example showing all of these; I haven’t included the code here, as the results are more interesting than the code itself. All of the names are the most obvious ones to pick, and I’d be surprised to see a different compiler pick a different option. I have found a difference between compilers for another aspect, however: determining when the compiler should fill in caller information attributes at all.

Implicit constructor invocations

The C# 5 language specification requires that caller information be used only when a function is explicitly invoked in source code, with the exception of query expressions that are deemed to be syntactic expansions. Other C# language constructs that are pattern based don’t apply to methods with optional parameters anyway, but constructor initializers definitely do. (Deconstruction is a C# 7 feature described in section 12.2.) The language specification calls out constructors as an example in which caller member information isn’t provided by the compiler unless the call is explicit. The following listing shows a single abstract base class with a constructor using caller member information and three derived classes.

Listing 7.7. Caller information in a constructor
public abstract class BaseClass
{
    protected BaseClass(                                    1
        [CallerFilePath] string file = "Unspecified file",         
        [CallerLineNumber] int line = -1,                          
        [CallerMemberName] string member = "Unspecified member")   
    {
        Console.WriteLine("{0}:{1} - {2}", file, line, member);
    }
}

public class Derived1 : BaseClass { }                       2

public class Derived2 : BaseClass
{
    public Derived2() { }                                   3
}

public class Derived3 : BaseClass
{
    public Derived3() : base() {}                           4
}

  • 1 Base class constructor uses caller info attributes.
  • 2 Parameterless constructor is added implicitly.
  • 3 Constructor with implicit call to base()
  • 4 Explicit call to base

With Roslyn, only Derived3 will result in real caller information being shown. Both Derived1 and Derived2, in which the call to the BaseClass constructor is implicit, use the default values specified in the parameters rather than providing the filename, line number, and member name.

This is in line with the C# 5 specification, but I’d argue it’s a design flaw. I believe most developers would expect the three derived classes to be precisely equivalent. Interestingly, the Mono compiler (mcs) currently prints the same output for each of these derived classes. We’ll have to wait to see whether the language specification changes, the Mono compiler changes, or the incompatibility continues into the future.

Query expression invocations

As I mentioned before, the language specification calls out query expressions as one place where caller information is provided by the compiler even though the call is implicit. I doubt that this will be used often, but I’ve provided a complete example in the downloadable source code. It requires more code than would be sensible to include here, but its use looks like the following listing.

Listing 7.8. Caller information in query expressions
string[] source =
{
    "the", "quick", "brown", "fox",
    "jumped", "over", "the", "lazy", "dog"
};
var query = from word in source               1
            where word.Length > 3             1
            select word.ToUpperInvariant();   1
Console.WriteLine("Data:");
Console.WriteLine(string.Join(", ", query));  2
Console.WriteLine("CallerInfo:");
Console.WriteLine(string.Join(                3
    Environment.NewLine, query.CallerInfo));  3

  • 1 Query expression using methods capturing caller information
  • 2 Logs the data
  • 3 Logs the caller information of the query

Although it contains a regular query expression, I’ve introduced new extension methods (in the same namespace as the example, so they’re found before the System.Linq ones) containing caller information attributes. The output shows that the caller information is captured in the query as well as the data itself:

Data:
QUICK, BROWN, JUMPED, OVER, LAZY
CallerInfo:
CallerInfoLinq.cs:91 - Main
CallerInfoLinq.cs:92 - Main

Is this useful? Probably not, to be honest. But it does highlight that when the language designers introduced the feature, they had to carefully consider a lot of situations. It would’ve been annoying if someone had found a good use for caller information from query expressions, but the specification hadn’t made it clear what should happen. We have one final kind of member invocation to consider, which feels to me like it’s even more subtle than constructor initializers and query expressions: attribute instantiation.

Attributes with caller information attributes

I tend to think about applying attributes as just specifying extra data. It doesn’t feel like it’s invoking anything, but attributes are code too, and when an attribute object is constructed (usually to be returned from a reflection call), that calls constructors and property setters. What counts as the caller if you create an attribute that uses caller information attributes in its constructor? Let’s find out.

First, you need an attribute class. This part is simple and is shown in the following listing.

Listing 7.9. Attribute class that captures caller information
[AttributeUsage(AttributeTargets.All)]
public class MemberDescriptionAttribute : Attribute
{
    public MemberDescriptionAttribute(
        [CallerFilePath] string file = "Unspecified file",
        [CallerLineNumber] int line = 0,
        [CallerMemberName] string member = "Unspecified member")
    {
        File = file;
        Line = line;
        Member = member;
    }

    public string File { get; }
    public int Line { get; }
    public string Member { get; }

    public override string ToString() =>
        $"{Path.GetFileName(File)}:{Line} - {Member}";
}

For brevity, this class uses a few features from C# 6, but the interesting aspect for now is that the constructor parameters use caller information attributes.

What happens when you apply our new MemberDescriptionAttribute? In the next listing, let’s apply it to a class and various aspects of a method and then see what you get.

Listing 7.10. Applying the attribute to a class and a method
using MDA = MemberDescriptionAttribute;                         1

[MemberDescription]                                             2
class CallerNameInAttribute                                     2
{
    [MemberDescription]                                         3
    public void Method<[MemberDescription] T>(                  3
        [MemberDescription] int parameter) { }                  3

    static void Main()
    {
        var typeInfo = typeof(CallerNameInAttribute).GetTypeInfo();
        var methodInfo = typeInfo.GetDeclaredMethod("Method");
        var paramInfo = methodInfo.GetParameters()[0];
        var typeParamInfo =
            methodInfo.GetGenericArguments()[0].GetTypeInfo();
        Console.WriteLine(typeInfo.GetCustomAttribute<MDA>());
        Console.WriteLine(methodInfo.GetCustomAttribute<MDA>());
        Console.WriteLine(paramInfo.GetCustomAttribute<MDA>());
        Console.WriteLine(typeParamInfo.GetCustomAttribute<MDA>());
    }
}

  • 1 Helps keep the reflection code short
  • 2 Applies the attribute to a class
  • 3 Applies the attribute to a method in various ways

The Main method uses reflection to fetch the attribute from all the places you’ve applied it. You could apply MemberDescriptionAttribute to other places: fields, properties, indexers, and the like. Feel free to experiment with the downloadable code to find out exactly what happens. What I find interesting is that the compiler is perfectly happy to capture the line number and file path in all cases, but it doesn’t use the class name as the member name, so the output is as follows:

CallerNameInAttribute.cs:36 - Unspecified member
CallerNameInAttribute.cs:39 - Method
CallerNameInAttribute.cs:40 - Method
CallerNameInAttribute.cs:40 - Method

Again, this is in the C# 5 specification, to the extent that it specifies the behavior when the attribute is applied to a function member (method, property, event, and so on) but not to a type. Perhaps it would’ve been more useful to include types here as well. They’re defined to be members of namespaces, so it’s not unreasonable for a member name to map to a type name.

Just to reiterate, the reason I included this section was more than for the sake of completeness. It highlights some interesting language choices. When is it okay for language design to accept limitations to avoid implementation costs? When is it reasonable for a language design choice to conflict with user expectations? When does it make sense for the specification to explicitly turn a decision into an implementation choice? At a meta level, how much time should the language design team spend to specify corner cases for a relatively minor feature? One final piece of practical detail remains before we close the chapter: enabling this feature on frameworks where the attributes don’t exist.

7.2.5. Using caller information attributes with old versions of .NET

Hopefully, by now most readers will be targeting .NET 4.5+ or .NET Standard 1.0+, both of which contain the caller information attributes. But in some cases, you’re still able to use a modern compiler but need to target old frameworks.

In these cases, you can still use the caller information attributes, but you need to make the attributes available to the compiler. The simplest way of doing this is to use the Microsoft.Bcl NuGet package, which provides the attributes and many other features provided by later versions of the framework.

If you can’t use the NuGet package for some reason, you can provide the attributes yourself. They’re simple attributes with no parameters or properties, so you can copy the declaration directly from the API documentation. They still need to be in the System.Runtime.CompilerServices namespace. To avoid type collisions, you’ll want to make sure these are available only when the system-provided attributes aren’t available. This can be tricky (as all versioning tends to be), and the details are beyond the scope of this book.

When I started writing this chapter, I hadn’t expected to write as much about caller information attributes as I ended up with. I can’t say I use the feature much in my day-to-day work, but I find the design aspects fascinating. This isn’t in spite of it being a minor feature; it’s because it’s a minor feature. You’d expect major features—dynamic typing, generics, async/await—to require significant language design work, but minor features can have all kinds of corner cases, too. Features often interact with each other, so one of the dangers of introducing a new feature is that it might make a future feature harder to design or implement.

Summary

  • Captured foreach iteration variables are more useful in C# 5.
  • You can use caller information attributes to ask the compiler to fill in parameters based on the caller’s source file, line number, and member name.
  • Caller information attributes demonstrate the level of detail that language design often requires.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset