I see the question was asked in 2011. Now it's 2019 and there are 2 options to retrieve local variabls from a method.
First let's define VariableInfo
to keep local variable parameters:
public class VariableInfo : IEquatable<VariableInfo>
{
public int Index { get; }
public string Name { get; }
public Type Type { get; }
public VariableInfo(int index, Type type, string name) =>
(Index, Type, Name) = (index, type, name);
public override bool Equals(object obj) =>
Equals(obj as VariableInfo);
public bool Equals(VariableInfo info) =>
info != null &&
Index.Equals(info.Index) &&
Name.Equals(info.Name) &&
Type.Equals(info.Type);
public override int GetHashCode()
{
unchecked
{
var hash = 17;
hash = 23 * hash + Index.GetHashCode();
hash = 23 * hash + Name.GetHashCode();
hash = 23 * hash + Type.GetHashCode();
return hash;
}
}
public override string ToString() =>
$"Index {Index}, Type {Type}, Name {Name}";
}
Then let's define ILocalsReader
interface as folows:
public interface ILocalsReader
{
VariableInfo[] Read(MethodBase info);
}
Now one can implement it with Microsoft.Samples.Debugging.CorApi
like this:
public class MicrosoftDebuggingReader : ILocalsReader
{
public VariableInfo[] Read(MethodBase info)
{
var il = info.GetMethodBody().LocalVariables.ToArray();
return SymbolAccess
.GetReaderForFile(info.DeclaringType.Assembly.Location)
.GetMethod(new SymbolToken(info.MetadataToken))
.RootScope
.GetInnerScopesRecursive()
.SelectMany(scope => scope.GetLocals())
.Select(local =>
new VariableInfo(local.AddressField1,
il[local.AddressField1].LocalType,
local.Name))
.ToArray();
}
}
Where GetInnerScopesRecursive
is an extension method:
internal static class SymbolScopeExtensions
{
public static IEnumerable<ISymbolScope> GetInnerScopesRecursive(this ISymbolScope scope)
{
yield return scope;
foreach (var innerScope in scope.GetChildren()
.SelectMany(innerScope => innerScope.GetInnerScopesRecursive()))
yield return innerScope;
}
}
Remember to build against x64
.
Another option is using Mono.Cecil
like this:
public class MonoCecilReader : ILocalsReader
{
public VariableInfo[] Read(MethodBase info)
{
var method = info.GetMethodDefinition();
method.Module.ReadSymbols();
var pdb = Path.ChangeExtension(info.DeclaringType.Assembly.Location, "pdb");
new PdbReaderProvider().GetSymbolReader(method.Module, pdb)
.Read(method);
var il = info.GetMethodBody().LocalVariables;
return Read(method, il);
}
public VariableInfo[] Read(MethodDefinition method, IList<LocalVariableInfo> il)
{
return method
.DebugInformation
.Scope
.GetInnerScopesRecursive()
.SelectMany(scope => scope.Variables)
.Select(local =>
new VariableInfo(local.Index,
il[local.Index].LocalType,
local.Name))
.ToArray();
}
}
Where GetMethodDefinition
is an extension method:
public static class MethodDefinitionExtensions
{
public static MethodDefinition GetMethodDefinition(this MethodBase info) =>
AssemblyDefinition
.ReadAssembly(info.DeclaringType.Assembly.Location)
.Modules
.SelectMany(module => module.GetTypes())
.Single(type => type.FullNameMatches(info.DeclaringType))
.Methods
.FirstOrDefault(method =>
method.Name.Equals(info.Name) &&
method.ReturnType.FullName.Equals(info.GetReturnType().FullName) &&
method.Parameters.Select(parameter => parameter.ParameterType.FullName)
.SequenceEqual(info.GetParameters().Select(parameter => parameter.ParameterType.FullName)));
}
And GetReturnType
is an extension method:
public static class MethodBaseExtensions
{
public static Type GetReturnType(this MethodBase method)
{
if (method is MethodInfo info)
return info.ReturnType;
if (method is ConstructorInfo ctor)
return typeof(void);
throw new ArgumentException($"Argument {nameof(method)} has unsupported type {method.GetType()}.");
}
}
And FullNameMatches
is an extension method:
internal static class TypeDefinitionExtensions
{
public static bool FullNameMatches(this TypeDefinition typeDefinition, Type type) =>
typeDefinition.FullName.Replace("/", "").Equals(type.FullName.Replace("+", ""));
}
And GetInnerScopesRecursive
is an extension method:
internal static class ScopeDebugInformationExtensions
{
public static IEnumerable<ScopeDebugInformation> GetInnerScopesRecursive(this ScopeDebugInformation scope)
{
yield return scope;
foreach (var innerScope in scope.Scopes
.SelectMany(innerScope => innerScope.GetInnerScopesRecursive()))
yield return innerScope;
}
}
Usage:
class Program
{
static void Main(string[] args)
{
var info = new Action<string>(Foo).GetMethodInfo();
Console.WriteLine("\tMicrosoft.Samples.Debugging.CorSymbolStore");
foreach (var v in new MicrosoftDebuggingReader().Read(info))
Console.WriteLine(v);
Console.WriteLine("\tMono.Cecil");
foreach (var v in new MonoCecilReader().Read(info))
Console.WriteLine(v);
}
public static void Foo(string s)
{
for (int i; ;)
for (double j; ;)
for (bool k; ;)
for (object m = 0; ;)
for (DateTime n; ;) { }
}
}
Gives:
Microsoft.Samples.Debugging.CorSymbolStore
Index 0, Type System.Int32, Name i
Index 1, Type System.Double, Name j
Index 2, Type System.Boolean, Name k
Index 3, Type System.Object, Name m
Index 4, Type System.DateTime, Name n
Mono.Cecil
Index 0, Type System.Int32, Name i
Index 1, Type System.Double, Name j
Index 2, Type System.Boolean, Name k
Index 3, Type System.Object, Name m
Index 4, Type System.DateTime, Name n
Note:
Microsoft.Samples.Debugging.CorApi
has ~9k downloads and was last updated on 10.12.2011
Mono.Cecil
has ~3415k downloads and latest commit on 05.08.2019