C# Reflection and Finding All References

C# reflection and finding all references

To find out where a method MyClass.Foo() is used, you have to analyse all classes of all assemblies that have a reference to the assembly that contains MyClass. I wrote a simple proof of concept of how this code can look like. In my example I used this library (it's just a single .cs file) written by Jb Evain:

I wrote a little test class to analyse:

public class TestClass
{
public void Test()
{
Console.WriteLine("Test");
Console.Write(10);
DateTime date = DateTime.Now;
Console.WriteLine(date);
}
}

And I wrote this code to print out all the methods used within TestClass.Test():

MethodBase methodBase = typeof(TestClass).GetMethod("Test");
var instructions = MethodBodyReader.GetInstructions(methodBase);

foreach (Instruction instruction in instructions)
{
MethodInfo methodInfo = instruction.Operand as MethodInfo;

if(methodInfo != null)
{
Type type = methodInfo.DeclaringType;
ParameterInfo[] parameters = methodInfo.GetParameters();

Console.WriteLine("{0}.{1}({2});",
type.FullName,
methodInfo.Name,
String.Join(", ", parameters.Select(p => p.ParameterType.FullName + " " + p.Name).ToArray())
);
}
}

It gave me the following output:

System.Console.WriteLine(System.String value);
System.Console.Write(System.Int32 value);
System.DateTime.get_Now();
System.Console.WriteLine(System.Object value);

This example is obviously far from complete, because it doesn't handle ref and out parameters, and it doesn't handle generic arguments. I am sure that forgot about other details as well. It just shows that it can be done.

Find all property references using reflection

You can achieve this by parsing the methodbody of each method and search for the respective metadata token. Have a look at this example it will print out the offsets of all instructions using the searched method token.

namespace TokenSearch
{
internal static class Program
{
private static void Main()
{
var token = typeof (Class1).GetProperty("TargetProp").GetGetMethod().MetadataToken;

const BindingFlags findAll = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.Static;
var references =
typeof (Program).Assembly.ManifestModule.GetTypes()
.SelectMany(x => x.GetMethods(findAll).Cast<MethodBase>().Union(x.GetConstructors(findAll)))
.ToDictionary(y => y, y => y.GetMethodUsageOffsets(token).ToArray())
.Where(z => z.Value.Length > 0).ToList();

foreach (var kv in references)
{
Console.WriteLine(
$"{kv.Key.DeclaringType}::{kv.Key.Name}: {string.Join(" ", kv.Value.Select(x => $"0x{x:x}"))}");
}
}
}

//some tests
public class Class1
{
public string TargetProp { get; set; }

private void TestMethod()
{
TargetProp = "123";
var x = TargetProp;
var y = TargetProp;
}
}

public class Class2
{
private string c1 = new Class1().TargetProp;

public void MoreMethods()
{
var c = new Class1();
var x = c.TargetProp;
}

public void CantFindThis()
{
var c = new Class1();
var x = c.ToString();
}
}

public static class Extensions
{
private static readonly Dictionary<short, OpCode> OpcodeDict =
typeof (OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static)
.Select(x => (OpCode) x.GetValue(null))
.ToDictionary(x => x.Value, x => x);

public static IEnumerable<short> GetMethodUsageOffsets(this MethodBase mi, int token)
{
var il = mi.GetMethodBody()?.GetILAsByteArray();
if (il == null) yield break;
using (var br = new BinaryReader(new MemoryStream(il)))
{
while (br.BaseStream.Position < br.BaseStream.Length)
{
var firstByte = br.ReadByte();
var opcode =
OpcodeDict[
firstByte != 0xFE
? firstByte
: BitConverter.ToInt16(new[] {br.ReadByte(), firstByte}, 0)];
switch (opcode.OperandType)
{
case OperandType.ShortInlineBrTarget:
case OperandType.ShortInlineVar:
case OperandType.ShortInlineI:
br.ReadByte();
break;
case OperandType.InlineVar:
br.ReadInt16();
break;
case OperandType.InlineField:
case OperandType.InlineType:
case OperandType.ShortInlineR:
case OperandType.InlineString:
case OperandType.InlineSig:
case OperandType.InlineI:
case OperandType.InlineBrTarget:
br.ReadInt32();
break;
case OperandType.InlineI8:
case OperandType.InlineR:
br.ReadInt64();
break;
case OperandType.InlineSwitch:
var size = (int) br.ReadUInt32();
br.ReadBytes(size*4);
break;
case OperandType.InlineMethod:
case OperandType.InlineTok:
if (br.ReadInt32() == token)
{
yield return (short) (br.BaseStream.Position - 4 - opcode.Size);
}
break;
}
}
}
}
}
}

Console output:

TokenSearch.Class1::TestMethod: 0xe 0x15
TokenSearch.Class2::MoreMethods: 0x8
TokenSearch.Class2::.ctor: 0x6

ILdasm output of Class1::TestMethod for reference:

.method private hidebysig instance void  TestMethod() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x21d0
// Code size 28 (0x1c)
.maxstack 2
.locals init ([0] string x,
[1] string y)
IL_0000: /* 00 | */ nop
IL_0001: /* 02 | */ ldarg.0
IL_0002: /* 72 | (70)000037 */ ldstr "123"
IL_0007: /* 28 | (06)000003 */ call instance void TokenSearch.Class1::set_TargetProp(string)
IL_000c: /* 00 | */ nop
IL_000d: /* 02 | */ ldarg.0
IL_000e: /* 28 | (06)000002 */ call instance string TokenSearch.Class1::get_TargetProp()
IL_0013: /* 0A | */ stloc.0
IL_0014: /* 02 | */ ldarg.0
IL_0015: /* 28 | (06)000002 */ call instance string TokenSearch.Class1::get_TargetProp()
IL_001a: /* 0B | */ stloc.1
IL_001b: /* 2A | */ ret
} // end of method Class1::TestMethod

A full implementation of a method body parser can be found in Mono.Reflection: MethodBodyReader.cs

C# Reflection: Obtain a list of references to a specific instance?

Afaik it is totally impossible without a start element.

DANGER READING BELOW; IS HUGELY INEFFICIENT BUT DOES THE JOB

If you on the other hand have a start element, you can use reflection to traverse all properties and fields of reference type and see if they are referencing to this, ie:

Object.ReferenceEquals(this, obj2);

And then you do the same thing recursively with properties and fields of both value and reference type.

I don't have more time on my hands right now, but I can chime back in and provide some snippets if needed.

How can I find all references to fields defined by a C# 9 record?

This is a bug (or not implemented feature if you want call it that way) in Roslyn.

The semantics around records is a mess. See this feedback, this issue, this issue, this issue, this issue

Is there a way of identifying if a given class has any references to another class?

I assume that these are public instance properties. If you want something else, you can adapt the BindingFlags.

The idea of my solution is to test whether the type of the property is generic. If yes, it tests whether one of the type arguments is typeof(A). However, It does not test nested generic types like List<List<A>>. This would require a recursive approach.

var props = typeof(C).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.PropertyType.IsGenericType &&
p.PropertyType.GenericTypeArguments.Any(t => t == typeof(A)));
foreach (var prop in props) {
Console.WriteLine(prop);
}

prints:

System.Collections.Generic.List`1[MyNamespace.A] Hellos


More specifically testing for enumerations of A using this type hierarchy:

class A
{
public string Hello { get; set; }
}

class AA : A
{
}

class B
{
public List<A> Hellos { get; set; }
public List<AA> AAHellos { get; set; }
}

class D : B
{
public A randomthing { get; set; }
}

class C : D
{
public string OtherThing { get; set; }
}

Test:

var c = new C {
Hellos = new List<A> {
new A { Hello = "Hello" },
new A { Hello = "World" }
},
AAHellos = new List<AA> {
new AA { Hello = "Hello" },
new AA { Hello = "World AA" }
}
};
var enumerableOfA = typeof(IEnumerable<A>);
var props = c.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => enumerableOfA.IsAssignableFrom(p.PropertyType));
foreach (var prop in props) {
Console.WriteLine(prop);
var list = (IEnumerable<A>)prop.GetValue(c);
foreach (var item in list) {
Console.WriteLine(item.Hello);
}
}

prints:

System.Collections.Generic.List`1[MyNamespace.A] Hellos

Hello

World

System.Collections.Generic.List`1[MyNamespace.AA] AAHellos

Hello AA

World AA

It might be surprising that this returns lists of AA as well. It happens because of the out keyword in the declaration of the interface IEnumerable<out T> making it covariant.

This will, however, not include, e.g. Dictionary<int,A>, because the dictionary implements IEnumerable<KeyValuePair<int,A>>. My first solution would return such a property, but then it would require more work to get dictionary entries.

Get Reference to Field from Reflection

Part of the problem you are experiencing is due to the fact those field values are structs. You only end up operating on copies of them. But we can get around this by building a delegate that accepts as its only parameter an object of the containing type (the type who's fields you are inspecting). This delegate will in turn call the method you are trying to invoke, passing the object's field under the hood with ref.

This solution below assumes that the methods you want to invoke (ImGui.Drag3, ImGui.Checkbox) always have two parameters -- string name and ref T value. In other words, a hypothetical method that operated on int fields would have to be declared as ImGui.DoSomethingToInt(string name, ref int value)

using System.Linq.Expressions;
using System.Reflection;
using System.Collection.Generic;

public static class ComponentHelpers
{
// helper function to get the MethodInfo for the method we want to call
private static MethodInfo GetStaticMethod(Expression<Action> expression)
{
if (expression.Body is MethodCallExpression body && body.Method.IsStatic)
return body.Method;

throw new InvalidOperationException("Expression must represent a static method");
}

// helper field we can use in calls to GetStaticMethod
private static class Ref<T>
{
public static T Value;
}

// Define which method we want to call based on the field's type
// each of these methods must take 2 parameters (string + ref T)
private static readonly Dictionary<Type, MethodInfo> Methods = new Dictionary<Type, MethodInfo>
{
[typeof(Vector3)] = GetStaticMethod(() => ImGui.Drag3(default, ref Ref<Vector3>.Value)),
[typeof(bool)] = GetStaticMethod(() => ImGui.Checkbox(default, ref Ref<bool>.Value))
};

// store the compiled delegates so that we only build/compile them once
private static readonly Dictionary<FieldInfo, Action<Component>> Delegates = new Dictionary<FieldInfo, Action<Component>>();

// this method will either build us a delegate, return one we've already built
// or will return null if we have not defined a method for the specific type
public static Action<Component> GetActionFor(FieldInfo field)
{
if (!Methods.TryGetValue(field.FieldType, out var method))
return null;

if (Delegates.TryGetValue(field, out var del))
return del;

// type the parameter as the base class Component
var param = Expression.Parameter(typeof(Component), "x");

var lambda = Expression.Lambda<Action<Component>>(
Expression.Call(
method,
// Pass the field's name as the first parameter
Expression.Constant(field.Name, typeof(string)),
// pass the field as the second parameter
Expression.Field(
// cast to the actual type so we can access fields of inherited types
Expression.Convert(param, field.ReflectedType),
field
)
),
param
);

return Delegates[field] = lambda.Compile();

}
}

Once we've done that, we can update your main loop to look like the following:

var fields = c.GetType().GetFields();
foreach (var field in fields)
{
var action = ComponentHelpers.GetActionFor(field);
if (action == null) // no method defined
continue;
// invoke the function passing in the object itself
action(c);
}


Related Topics



Leave a reply



Submit