Good things are hard to kill. One of the most useful predefined compiler macros in C/C++ were __FILE__ and __LINE__ which do expand to the compilation units file name and line number where this value is encountered by the compiler. After 4.5 versions of .NET we are on par with C/C++ again. It is of course not a simple compiler expandable macro it is an attribute but it does serve exactly the same purpose. Now we do get CallerLineNumberAttribute == __LINE__ CallerFilePathAttribute == __FILE__ CallerMemberNameAttribute == __FUNCTION__ (MSVC Extension) The most important one is CallerMemberNameAttribute which is very useful to implement the INotifyPropertyChanged interface without the need to hard code the name of the property anymore. Now you can simply decorate your change method with the new CallerMemberName attribute and you get the property name as string directly inserted by the C# compiler at compile time. public string UserName
{
get { return _userName; }
set { _userName=value;
RaisePropertyChanged(); // no more RaisePropertyChanged(“UserName”)!
}
}
protected void RaisePropertyChanged([CallerMemberName] string member = "")
{
var copy = PropertyChanged;
if(copy != null)
{
copy(new PropertyChangedEventArgs(this, member));
}
}
Nice and handy. This was obviously the prime reason to implement this feature in the C# 5.0 compiler. You can repurpose this feature for tracing to get your hands on the method name of your caller along other stuff very fast now. All infos are added during compile time which is much faster than other approaches like walking the stack.
The example on MSDN shows the usage of this attribute with an example
public static void TraceMessage(string message, [CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
Console.WriteLine("Hi {0} {1} {2}({3})", message, memberName, sourceFilePath, sourceLineNumber);
}
When I do think of tracing I do usually want to have a API which allows me to
Trace method enter and leave
Trace messages with a severity like Info, Warning, Error
When I do print a trace message it is very useful to print out method and type name as well. So your API must either be able to pass the method and type name as strings or extract it automatically via walking back one Stackframe and fetch the infos from there. The first glaring deficiency is that there is no CallerTypeAttribute yet because the C# compiler team was not satisfied with its performance.
A usable Trace Api might therefore look like
enum TraceTypes
{
None = 0,
EnterLeave = 1 << 0,
Info = 1 << 1,
Warn = 1 << 2,
Error = 1 << 3
}
class Tracer : IDisposable
{
string Type;
string Method;
public Tracer(string type, string method)
{
Type = type;
Method = method;
if (IsEnabled(TraceTypes.EnterLeave,Type, Method))
{
}
}
private bool IsEnabled(TraceTypes traceTypes, string Type, string Method)
{
// Do checking here if tracing is enabled
return false;
}
public void Info(string fmt, params object[] args)
{
}
public void Warn(string fmt, params object[] args)
{
}
public void Error(string fmt, params object[] args)
{
}
public static void Info(string type, string method, string fmt, params object[] args)
{
}
public static void Warn(string type, string method, string fmt, params object[] args)
{
}
public static void Error(string type, string method, string fmt, params object[] args)
{
}
public void Dispose()
{
// trace method leave
}
}
This minimal trace API is very fast but hard to maintain since you need to pass in the type and method name as hard coded strings which can change from time to time. But now we have at least CallerMemberName to rid of the explicit method parameter right? Not really. Since any acceptable usable trace Api should have a method signature like Tracexxx(… string fmt, params [] object args) we not able to add additional optional parameters after the args array. If we would put it before the format string we would need to make it optional as well which would mean the compiler would need to figure out what our trace message and arguments are (not likely) or we would need to specify everything explicitly just like before .
There are ways around this by providing a myriad of overloads which in the end are routed to the very same method but that is ugly. I am not sure if nobody inside MS agrees that the above API is reasonable to have or (more likely) that the whole talk about you can use this feature for diagnostic purposes was not a core feature at all but a simple byproduct of making the life of INotifyPropertyChanged implementers easier.
A way around this would be to allow for variable argument arrays after the params keyword another set of optional arguments which are always filled by the compiler but I do not know if this is an easy one.
The thing I am missing much more is the not provided CallerType attribute. But not in the way you would think of. In the API above I did add some filtering based on method and type to stay as fast as possible for types where tracing is not enabled at all. It should be no more expensive than an additional method call and a bool variable check if tracing for this type is enabled at all. The data is tightly bound to the calling type and method and should therefore become part of the static type instance. Since extending the CLR type system for tracing is not something I do expect to happen I have come up with an alternative approach which allows me basically to attach run time data to any existing type object in super fast way. The key to success is the usage of generics.
class Tracer<T> : IDisposable
{
string Method;
public Tracer(string method)
{
if (TraceData<T>.Instance.Enabled.HasFlag(TraceTypes.EnterLeave))
{
}
}
public void Dispose()
{
if (TraceData<T>.Instance.Enabled.HasFlag(TraceTypes.EnterLeave))
{
}
}
public static void Info(string fmt, params object[] args)
{
}
/// <summary>
/// Every type gets its own instance with a fresh set of variables to describe the
/// current filter status.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class TraceData<UsingType>
{
internal static TraceData<UsingType> Instance = new TraceData<UsingType>();
public bool IsInitialized = false; // flag if we need to reinit the trace data in case of reconfigured trace settings at runtime
public TraceTypes Enabled = TraceTypes.None; // Enabled trace levels for this type
}
}
We do not need to pass the type as string or Type object to the trace Api. Instead we define a generic Api that accepts the using type as generic parameter. Then we can create a TraceData static instance which is due to the nature of generics a fresh instance for every new type parameter. My tests on my home machine have shown that this approach is as fast as a simple bool flag check. If you have an application with many types using tracing you do not want to bring the app down by simply enabling tracing for one special rarely used type. The trace filter performance for the types which are not enabled must be therefore the fasted code path. This approach has the nice side effect that if you store the TraceData instances in one global list you can reconfigure tracing at runtime safely by simply setting the IsInitialized flag to false. A similar effect can be achieved with a global static Dictionary<Type,TraceData> object but big hash tables have random memory access semantics which is bad for cache locality and you always need to pay for the lookup which involves hash code generation, equality check and an indexed array access.
The generic version is wicked fast and allows you to add more features to your tracing Api with minimal perf overhead. But it is cumbersome to write the generic type argument always explicitly and worse if you do refactor code and move parts of it to other classes it might be that you cannot configure tracing correctly. I would like therefore to decorate my type with an attribute
[CallerType]
class Tracer<T> : IDisposable
to tell the compiler to fill in the generic type argument automatically.
class Program
{
static void Main(string[] args)
{
using (var t = new Tracer()) // equivalent to new Tracer<Program>()
{
That would be really useful and super fast since you do not need to pass any type object around but you do have full type infos at hand. This change would be breaking if another non generic type exists in the same namespace where now the generic counterpart would be preferred. But this is an acceptable risk in my opinion since you can today already get conflicts if two generic types of the same name are defined in different namespaces. This would be only a variation of this issue.
When you do think about this further you can add more features like to trace the exception in your Dispose method if the method is left with an exception with that little trick I did write some time ago. You can think of tracing as a super fast and configurable switch to write data to an output destination or to execute alternative actions. With such an infrastructure you can e.g.
Reconfigure tracing at run time.
Take a memory dump when a specific method is left with a specific exception.
Throw an exception when a specific trace statement is hit (useful for testing error conditions).
Execute a passed delegate which e.g. dumps additional state when enabled.
Write data to an in memory ring buffer and dump it when specific events do occur (e.g. method is left with an exception, triggered from outside).
Write data to an output device.
….
This stuff is really useful to have when your code is in production on a mission critical server and you need to find the root cause of sporadic crashes of your application. It could be a buggy graphics card driver which throws access violations into your application (ok with .NET 4 not anymore except if you enable a compatibility flag) where you would like to have a minidump or you have reached after two weeks of operation a state where you need a full memory dump at a specific point in time in the middle of an transaction.
At my older machine I do get with this super fast approach 50 million traces/s when tracing is disabled. When I do know that tracing is enabled for this type I can walk the stack by using StackFrameHelper.GetStackFramesInternal to check further if a specific action or output device is configured for this method which is about 2-3 times faster than the regular StackTrace class. Even with one String.Format I am down to 3 million traces/s so performance is not so important anymore since I do want to do something now.
The CallerMemberName feature of the C# 5 compiler is nice but I would have preferred to get direct access to the MethodHandle and not to the stringified version of it. But I really would like to see a CallerType attribute implemented to fill in the generic type argument of the call site to augment the static CLR type data with run time data.