public
Created

Caller-dependent method level local statics implementation in C#

  • Download Gist
MethodStatics.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
 
namespace MethodStaticLocals
{
class ExpensiveObject
{
public ExpensiveObject()
{
Console.WriteLine( "Creating Expensive object" );
}
};
 
class MainClass
{
public static void Main( string[] args )
{
Expression<Action> call = () => Func( "hello" );
 
Invoke( call );
Invoke( call );
}
 
// caches handles for expresisons, as they are expensive to find.
static Dictionary<Expression, RuntimeMethodHandle> handleCache = new Dictionary<Expression, RuntimeMethodHandle>();
// static locals are managed per method handle
static Dictionary<RuntimeMethodHandle, Dictionary<string, object>> staticLocals = new Dictionary<RuntimeMethodHandle, Dictionary<string, object>>();
// redirects are individual for each expression tree
static Dictionary<Expression, Delegate> redirects = new Dictionary<Expression, Delegate>();
 
static void Invoke( Expression<Action> call )
{
if (call.Parameters != null && call.Parameters.Any())
throw new InvalidOperationException();
 
if (call.Body.NodeType != ExpressionType.Call)
throw new InvalidOperationException();
 
Delegate redirectedInvocation = SetupRedirectedInvocation( call );
 
redirectedInvocation.DynamicInvoke();
}
 
private static Delegate SetupRedirectedInvocation( Expression<Action> call )
{
Delegate redirectedInvocation;
if (!redirects.TryGetValue( call, out redirectedInvocation ))
{
RuntimeMethodHandle caller = SetupCaller( call );
 
Console.WriteLine( "Creating redirect for {0}", caller.Value );
MethodCallExpression callExpression = (MethodCallExpression)call.Body;
 
// add staticLocals dictionary as argument
var arguments = callExpression.Arguments.ToList();
arguments.Add( Expression.Constant( staticLocals[caller] ) );
 
// todo: dynamically find redirect
var redirect = MethodOf( () => Func( default( string ), default( Dictionary<string, object> ) ) );
 
LambdaExpression redirectedExpression = Expression.Lambda( Expression.Call( callExpression.Object, redirect, arguments ), new ParameterExpression[0] );
 
redirectedInvocation = redirectedExpression.Compile();
redirects.Add( call, redirectedInvocation );
}
return redirectedInvocation;
}
 
private static RuntimeMethodHandle SetupCaller( Expression<Action> call )
{
RuntimeMethodHandle caller;
if (!handleCache.TryGetValue( call, out caller ))
{
caller = new StackFrame( 1 ).GetMethod().MethodHandle;
handleCache.Add( call, caller );
staticLocals.Add( caller, new Dictionary<string, object>() );
}
return caller;
}
 
public static MethodInfo MethodOf( Expression<Action> expression )
{
MethodCallExpression body = (MethodCallExpression)expression.Body;
return body.Method;
}
 
[Obsolete( "do not call directly" )]
public static void Func( string arg )
{
}
 
private static void Func( string arg, Dictionary<string, object> staticLocals )
{
if (!staticLocals.ContainsKey( "expensive"))
{
staticLocals.Add( "expensive", new ExpensiveObject() );
}
 
ExpensiveObject obj = (ExpensiveObject)staticLocals["expensive"];
Console.WriteLine( "Func invoked: arg: {0}; expensive: {1}", arg, obj );
}
}
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.