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 record
s 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
What Is the Static Variable Initialization Order Across Classes in C#
Binding Listbox to List<Object> in Winforms
How to Prevent a SQL Injection Escaping Strings
.Net 4.0 and the Dreaded Onuserpreferencechanged Hang
How to Bind Crystal Report to Manually Created Dataset
Get SQL Query from Linq to SQL
Set Default Global JSON Serializer Settings
Why Can't I Assign a List<Derived> to a List<Base>
Gps Socket Communication (Concox)
How Accurate Is Thread.Sleep(Timespan)
Getting Attribute Value of an Xml Document Using C#
Why Doesn't Xmlserializer Support Dictionary
How to Execute Task in the Wpf Background While Able to Provide Report and Allow Cancellation
JSON.Net Serialize by Depth and Attribute
C#: Prepending to Beginning of a File
How to Sort a String of Text Followed by a Number Using Linq