HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Fun with CallerLineNumberAttribute: clever hack or terrible idea?

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
terribleideawithcleverfunhackcallerlinenumberattribute

Problem

One fun feature that was added (fairly) recently in .NET is the [CallerLineNumber] attribute, which, in conjunction with [CallerFilePath] and [CallerMemberName] can allow for some very nice logging methods.

I was thinking today, though, that they might be used for another interesting purpose. Consider the case where you have some event that should trigger on a Boolean transition. The classic implementation for this uses a field to keep track of the 'last state', then updates the field after performing the check. The annoying thing, though, is that you need a flag for every case and each flag needs a name. So I thought of something like this:

```
public sealed class IndexedBuilder
where T : class, new()
{
private readonly Dictionary _instances = new Dictionary();

public T Build(int index)
{
T instance;

lock (_instances)
{
if (!_instances.TryGetValue(index, out instance))
{
instance = new T();
_instances.Add(index, instance);
}
}

return instance;
}
}

public sealed class TransitionDetector
{
private readonly IndexedBuilder _builder = new IndexedBuilder();

public bool DetectUp(bool state, [CallerLineNumber] int lineNumber = 0)
{
return _builder.Build(lineNumber).DetectUp(state);
}

public bool DetectDown(bool state, [CallerLineNumber] int lineNumber = 0)
{
return _builder.Build(lineNumber).DetectDown(state);
}

private sealed class Context
{
private bool? _lastState;

public bool DetectUp(bool state)
{
bool result = state && (_lastState == false);
_lastState = state;
return result;
}

public bool DetectDown(bool state)
{
bool result = !state && (_lastState == true);
_lastState = state;
return result;
}
}
}

public static class Program
{
public

Solution

This is definitely an interesting use of [CallerLineNumber] but I think that the risk for people using it wrong resulting in hard to find bugs is to great. If you take an example from the issue you yourself mentioned.

if (detector.DetectUp(y > 100) ||
detector.DetectUp(x

Would not as one might think have the same behavior as

if (detector.DetectUp(y > 100) || detector.DetectUp(x < 500))
{...}


Another gotcha I found was that lambda functions does not give the line or name of where they are called but where they are defined. This makes sense but might not be what the user expects. The example below prints
Main: 10 and not HelpFun: 16`.

using System;
using System.Runtime.CompilerServices;

namespace CallerLineNumberTest
{
    class Program
    {
        static void Main(string[] args)
        {
            HelpFun(() => PrintLineNr());
            Console.ReadKey();
        }

        private static void HelpFun(Action a)
        {
            a();
        }

        private static void PrintLineNr([CallerMemberName] string name = "",
            [CallerLineNumber] int lineNumber = 0)
        {
            Console.WriteLine(name + ": " + lineNumber);
        }
    }
}

Code Snippets

if (detector.DetectUp(y > 100) || detector.DetectUp(x < 500))
{...}
using System;
using System.Runtime.CompilerServices;

namespace CallerLineNumberTest
{
    class Program
    {
        static void Main(string[] args)
        {
            HelpFun(() => PrintLineNr());
            Console.ReadKey();
        }

        private static void HelpFun(Action a)
        {
            a();
        }

        private static void PrintLineNr([CallerMemberName] string name = "",
            [CallerLineNumber] int lineNumber = 0)
        {
            Console.WriteLine(name + ": " + lineNumber);
        }
    }
}

Context

StackExchange Code Review Q#86920, answer score: 3

Revisions (0)

No revisions yet.