Get types used inside a C# method body

后端 未结 7 849
鱼传尺愫
鱼传尺愫 2020-12-01 16:12

Is there a way to get all types used inside C# method?

For example,

public int foo(string str)
{
    Bar bar = new Bar();
    string x = \"test\";
           


        
相关标签:
7条回答
  • 2020-12-01 16:59

    The closest thing to that that I can think of are expression trees. Take a look at the documentation from Microsoft.

    They are very limited however and only work on simple expressions and not full methods with statement bodies.

    Edit: Since the intention of the poster was to find class couplings and used types, I would suggest using a commercial tool like NDepend to do the code analysis as an easy solution.

    0 讨论(0)
  • 2020-12-01 17:04

    I just posted an extensive example of how to use Mono.Cecil to do static code analysis like this.

    I also show a CallTreeSearch enumerator class that can statically analyze call trees, looking for certain interesting things and generating results using a custom supplied selector function, so you can plug it with your 'payload' logic, e.g.

        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;
        }
    

    This leaves out a few details

    • implementation of IsBusinessCall, IsConstructorCall and TryResolve as these are trivial and serve as illustrative only

    Hope that helps

    0 讨论(0)
  • 2020-12-01 17:09

    I'm going to take this opportunity to post up a proof of concept I did because somebody told me it couldn't be done - with a bit of tweaking here and there, it'd be relatively trivial to extend this to extract out all referenced Types in a method - apologies for the size of it and the lack of a preface, but it's somewhat commented:

    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;
        }
    }
    
    0 讨论(0)
  • 2020-12-01 17:09

    As others have mentioned, if you had the DLL you could use something similar to what ILSpy does in its Analyze feature (iterating over all the IL instructions in the assembly to find references to a specific type).

    Otherwise, there is no way to do it without parsing the text into a C# Abstract Syntax Tree, AND employing a Resolver - something that can understand the semantics of the code well enough to know if "Bar" in your example is indeed a name of a type that is accessible from that method (in its "using" scope), or perhaps the name of a method, member field, etc... SharpDevelop contains a C# parser (called "NRefactory") and also contains such a Resolver, you can look into pursuing that option by looking at this thread, but beware that it is a fair amount of work to set it up to work right.

    0 讨论(0)
  • 2020-12-01 17:12

    With reflection you can get the method. This returns a MethodInfo object, and with this object you cannot get the types which are used in the method. So I think the answer is that you cannot get this native in C#.

    0 讨论(0)
  • 2020-12-01 17:13

    If you can access the IL for this method, you might be able to do something suitable. Perhaps look at the open source project ILSpy and see whether you can leverage any of their work.

    0 讨论(0)
提交回复
热议问题