Получить типы, используемые внутри тела метода C#
Есть ли способ получить все типы, используемые внутри метода C#?
Например,
public int foo(string str)
{
Bar bar = new Bar();
string x = "test";
TEST t = bar.GetTEST();
}
вернул бы: бар, строка и тест.
Все, что я могу сейчас получить - это текст метода, использующий EnvDTE.CodeFunction. Возможно, есть лучший способ добиться этого, чем пытаться разобрать этот код.
7 ответов
Я воспользуюсь этой возможностью, чтобы опубликовать доказательство концепции, которую я сделал, потому что кто-то сказал мне, что это не может быть сделано - с небольшими изменениями здесь и там, было бы относительно тривиально расширить это, чтобы извлечь все ссылки на типы в методе - извинения за размер и отсутствие предисловия, но это несколько прокомментировано:
void Main()
{
Func<int,int> addOne = i => i + 1;
Console.WriteLine(DumpMethod(addOne));
Func<int,string> stuff = i =>
{
var m = 10312;
var j = i + m;
var k = j * j + i;
var foo = "Bar";
var asStr = k.ToString();
return foo + asStr;
};
Console.WriteLine(DumpMethod(stuff));
Console.WriteLine(DumpMethod((Func<string>)Foo.GetFooName));
Console.WriteLine(DumpMethod((Action)Console.Beep));
}
public class Foo
{
public const string FooName = "Foo";
public static string GetFooName() { return typeof(Foo).Name + ":" + FooName; }
}
public static string DumpMethod(Delegate method)
{
// For aggregating our response
StringBuilder sb = new StringBuilder();
// First we need to extract out the raw IL
var mb = method.Method.GetMethodBody();
var il = mb.GetILAsByteArray();
// We'll also need a full set of the IL opcodes so we
// can remap them over our method body
var opCodes = typeof(System.Reflection.Emit.OpCodes)
.GetFields()
.Select(fi => (System.Reflection.Emit.OpCode)fi.GetValue(null));
//opCodes.Dump();
// For each byte in our method body, try to match it to an opcode
var mappedIL = il.Select(op =>
opCodes.FirstOrDefault(opCode => opCode.Value == op));
// OpCode/Operand parsing:
// Some opcodes have no operands, some use ints, etc.
// let's try to cover all cases
var ilWalker = mappedIL.GetEnumerator();
while(ilWalker.MoveNext())
{
var mappedOp = ilWalker.Current;
if(mappedOp.OperandType != OperandType.InlineNone)
{
// For operand inference:
// MOST operands are 32 bit,
// so we'll start there
var byteCount = 4;
long operand = 0;
string token = string.Empty;
// For metadata token resolution
var module = method.Method.Module;
Func<int, string> tokenResolver = tkn => string.Empty;
switch(mappedOp.OperandType)
{
// These are all 32bit metadata tokens
case OperandType.InlineMethod:
tokenResolver = tkn =>
{
var resMethod = module.SafeResolveMethod((int)tkn);
return string.Format("({0}())", resMethod == null ? "unknown" : resMethod.Name);
};
break;
case OperandType.InlineField:
tokenResolver = tkn =>
{
var field = module.SafeResolveField((int)tkn);
return string.Format("({0})", field == null ? "unknown" : field.Name);
};
break;
case OperandType.InlineSig:
tokenResolver = tkn =>
{
var sigBytes = module.SafeResolveSignature((int)tkn);
var catSig = string
.Join(",", sigBytes);
return string.Format("(SIG:{0})", catSig == null ? "unknown" : catSig);
};
break;
case OperandType.InlineString:
tokenResolver = tkn =>
{
var str = module.SafeResolveString((int)tkn);
return string.Format("('{0}')", str == null ? "unknown" : str);
};
break;
case OperandType.InlineType:
tokenResolver = tkn =>
{
var type = module.SafeResolveType((int)tkn);
return string.Format("(typeof({0}))", type == null ? "unknown" : type.Name);
};
break;
// These are plain old 32bit operands
case OperandType.InlineI:
case OperandType.InlineBrTarget:
case OperandType.InlineSwitch:
case OperandType.ShortInlineR:
break;
// These are 64bit operands
case OperandType.InlineI8:
case OperandType.InlineR:
byteCount = 8;
break;
// These are all 8bit values
case OperandType.ShortInlineBrTarget:
case OperandType.ShortInlineI:
case OperandType.ShortInlineVar:
byteCount = 1;
break;
}
// Based on byte count, pull out the full operand
for(int i=0; i < byteCount; i++)
{
ilWalker.MoveNext();
operand |= ((long)ilWalker.Current.Value) << (8 * i);
}
var resolved = tokenResolver((int)operand);
resolved = string.IsNullOrEmpty(resolved) ? operand.ToString() : resolved;
sb.AppendFormat("{0} {1}",
mappedOp.Name,
resolved)
.AppendLine();
}
else
{
sb.AppendLine(mappedOp.Name);
}
}
return sb.ToString();
}
public static class Ext
{
public static FieldInfo SafeResolveField(this Module m, int token)
{
FieldInfo fi;
m.TryResolveField(token, out fi);
return fi;
}
public static bool TryResolveField(this Module m, int token, out FieldInfo fi)
{
var ok = false;
try { fi = m.ResolveField(token); ok = true; }
catch { fi = null; }
return ok;
}
public static MethodBase SafeResolveMethod(this Module m, int token)
{
MethodBase fi;
m.TryResolveMethod(token, out fi);
return fi;
}
public static bool TryResolveMethod(this Module m, int token, out MethodBase fi)
{
var ok = false;
try { fi = m.ResolveMethod(token); ok = true; }
catch { fi = null; }
return ok;
}
public static string SafeResolveString(this Module m, int token)
{
string fi;
m.TryResolveString(token, out fi);
return fi;
}
public static bool TryResolveString(this Module m, int token, out string fi)
{
var ok = false;
try { fi = m.ResolveString(token); ok = true; }
catch { fi = null; }
return ok;
}
public static byte[] SafeResolveSignature(this Module m, int token)
{
byte[] fi;
m.TryResolveSignature(token, out fi);
return fi;
}
public static bool TryResolveSignature(this Module m, int token, out byte[] fi)
{
var ok = false;
try { fi = m.ResolveSignature(token); ok = true; }
catch { fi = null; }
return ok;
}
public static Type SafeResolveType(this Module m, int token)
{
Type fi;
m.TryResolveType(token, out fi);
return fi;
}
public static bool TryResolveType(this Module m, int token, out Type fi)
{
var ok = false;
try { fi = m.ResolveType(token); ok = true; }
catch { fi = null; }
return ok;
}
}
Если вы можете получить доступ к IL для этого метода, вы можете сделать что-то подходящее. Возможно, посмотрите на проект с открытым исходным кодом ILSpy и посмотрите, сможете ли вы использовать любую их работу.
Как уже упоминали другие, если бы у вас была DLL, вы могли бы использовать нечто похожее на то, что делает ILSpy в своей функции анализа (перебирая все инструкции IL в сборке, чтобы найти ссылки на определенный тип).
В противном случае, сделать это невозможно, не разбирая текст в абстрактном синтаксическом дереве C# и не используя Resolver - то, что может достаточно хорошо понять семантику кода, чтобы понять, действительно ли "Bar" в вашем примере является именем тип, который доступен из этого метода (в его области "using"), или, возможно, имя метода, поля члена и т. д. SharpDevelop содержит синтаксический анализатор C# (называемый "NRefactory"), а также содержит такой Resolver, вы можете посмотреть на эту возможность, взглянув на эту ветку, но помните, что для ее правильной работы достаточно усилий.
Я только что опубликовал обширный пример how to use Mono.Cecil to do static code analysis
как это.
Я также показываю класс-перечислитель CallTreeSearch, который может статически анализировать деревья вызовов, искать определенные интересные вещи и генерировать результаты, используя настраиваемую функцию селектора, так что вы можете подключить его с логикой "полезной нагрузки", например
static IEnumerable<TypeUsage> SearchMessages(TypeDefinition uiType, bool onlyConstructions)
{
return uiType.SearchCallTree(IsBusinessCall,
(instruction, stack) => DetectTypeUsage(instruction, stack, onlyConstructions));
}
internal class TypeUsage : IEquatable<TypeUsage>
{
public TypeReference Type;
public Stack<MethodReference> Stack;
#region equality
// ... omitted for brevity ...
#endregion
}
private static TypeUsage DetectTypeUsage(
Instruction instruction, IEnumerable<MethodReference> stack, bool onlyConstructions)
{
TypeDefinition resolve = null;
{
TypeReference tr = null;
var methodReference = instruction.Operand as MethodReference;
if (methodReference != null)
tr = methodReference.DeclaringType;
tr = tr ?? instruction.Operand as TypeReference;
if ((tr == null) || !IsInterestingType(tr))
return null;
resolve = tr.GetOriginalType().TryResolve();
}
if (resolve == null)
throw new ApplicationException("Required assembly not loaded.");
if (resolve.IsSerializable)
if (!onlyConstructions || IsConstructorCall(instruction))
return new TypeUsage {Stack = new Stack<MethodReference>(stack.Reverse()), Type = resolve};
return null;
}
Это оставляет некоторые детали
- реализация
IsBusinessCall
,IsConstructorCall
а такжеTryResolve
поскольку они тривиальны и служат только для иллюстрации
надеюсь, это поможет
Самое близкое к тому, о чем я могу думать, это деревья выражений. Посмотрите документацию от Microsoft.
Однако они очень ограничены и работают только с простыми выражениями, а не с полными методами с телами операторов.
Изменить: Так как целью постера было найти классовые связи и используемые типы, я бы предложил использовать коммерческий инструмент, такой как NDepend, чтобы сделать анализ кода в качестве простого решения.
С отражением вы можете получить метод. Это возвращает объект MethodInfo, и с этим объектом вы не можете получить типы, которые используются в методе. Поэтому я думаю, что ответ заключается в том, что вы не можете получить этот натив в C#.
Это определенно нельзя сделать с помощью отражения (GetMethod(), деревья выражений и т. Д.). Как вы упомянули, использование CodeModel в EnvDTE является вариантом, поскольку вы получаете построчное C#, но использовать его вне Visual Studio (то есть обрабатывать уже существующую функцию, а не в окне редактора) практически невозможно, ИМХО.
Но я могу порекомендовать Mono.Cecil, который может построчно обрабатывать код CIL (внутри метода), и вы можете использовать его для любого метода из любой сборки, на которую у вас есть ссылка. Затем вы можете проверить каждую строку, является ли она объявлением переменной (например, строка x = "test" или methodCall, и вы можете получить типы, включенные в эти строки.