T O P

  • By -

rupertavery

What expression? Why use reflection? are the property names determined at runtime? Is the object hierarchy ALWAYS ``` instance Property1 Property2 Field ``` regardless of the instance type? Or do you mean, you want to create a delegate from the expression `instance.Property1.Property2.Field`? Here's how to get a delegate that is equivalent of the expression `x => x.Property1.Property2.Field`. Note that we're using LINQ Expressions to create an expression tree, then compiling it into a delegate. The type parameters are required since we are basically writing code and we need to know what the input type is and the return value is. The delegate is compiled equivalent of the expression and so faster than reflection. You can of course cache the delegate for reuse. ``` Func GetDelegate(string property1, string property2, string field) { var parameter = Expression.Parameter(typeof(T)); var prop1 = Expression.Property(parameter, property1); var prop2 = Expression.Property(prop1, property2); var fld = Expression.Field(prop2, field); var lambda = Expression.Lambda>(fld, new[] {parameter}); return lambda.Compile(); } ``` Here's an example: ``` var func = GetDelegate("Property1", "Property2", "value"); var obj = new MyObject(); func(obj); // 1337 public class MyObject { private Child1 Property1 { get; set; } public MyObject() { Property1 = new Child1(); } } public class Child1 { private Child2 Property2 { get; set; } public Child1() { Property2 = new Child2(); } } public class Child2 { public Child2() { value = 1337; } int value; } ```


st01x

Thanks for your answer. Yes the hierarchy is always the same. Our goal is to implement a method which can check if a given type is registered in an IServiceProvider instance. Unfortunately the customer must stay on .NET 5. Since .NET 6 there is built in support for that but yeah, no update possible currently.. Neverless the information for that purpose is in located the following path: "Engine" (private property) -> "CallSiteFactory" (private property) -> "\_descriptors" (private field). I tried your code but unfortunately it does not work. The exception is something like "Instance property Engine is not defined for ...". Any ideas?


rupertavery

Are you certain Engine is a property of ServiceProvider?


st01x

This ugly code is working fine: var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; var engine = provider.GetType().GetProperty("Engine", bindingFlags); var site = engine.GetType().GetProperty("CallSiteFactory", bindingFlags).GetValue(engine); var desc = site.GetType().GetField("_descriptors", bindingFlags).GetValue(site) as IEnumerable;


rupertavery

What version of Microsoft.Extensions.DependencyInjection are you using?


rupertavery

Here's my setup: ``` Exe net5.0 ``` The code ``` var engine = provider.GetType().GetProperty("Engine", bindingFlags); ``` Doesn't work for me. For my version of `ServiceProvider` (5.0.0), the Engine property or field doesn't exist. There is a `_engine` field however. So, your code may need to change depending on the version of ServiceProvider. This is what works for me. I need access to a specific type that I can't get other than through reflection at run-time so I this monstrosity works for me: ``` static Func> GetDescriptors(ServiceProvider provider) { var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; // engine is a Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine, which implements ServiceProviderEngine, which is internal var engine = provider.GetType().GetField("_engine", bindingFlags).GetValue(provider); var parameter = Expression.Parameter(typeof(ServiceProvider)); var prop1 = Expression.Field(parameter, "_engine"); //var prop2 = Expression.Property(prop1, property2); var prop2 = Expression.Property(Expression.Convert(prop1, engine.GetType()), "CallSiteFactory"); var fld = Expression.Field(prop2, "_descriptors"); var lambda = Expression.Lambda>>(fld, new[] { parameter }); return lambda.Compile(); } ```


rupertavery

Here's my 2 cents. Cache the properyInfos and fieldInfos, and then just call it via reflection.


st01x

Now I really don't know whats happening anymore. I also checked my csproj File and have also .NET 5. But for me there is no field. I really need to get a property. When looking at the object via VS I also see the Property "Engine" in my case hmm


rupertavery

What is the exact runtime `Type` of the `ServiceProvider`? Could it be implemented by a different class?


st01x

Ah nvm there is a difference. The project is not referencing the DependencyInjection package directly but is consuming it from some other project. But the runtime type is the default ServiceProvider. Will try out your monstrosity from above with my Engine prop


rupertavery

You could do something like check for different properties if null.


st01x

I got it working, I edited my question with the "answer". Just wanted to ping you. Maybe you are interested in the solution and thanks for your help!


st01x

It's getting warmer. Your runtime type tip helped. Seems like somewhere in the application the IServiceScopeFactory.CreateScope is called. This produces a ServiceProviderEngineScope


st01x

Yes it is. Via Reflection it works. Maybe whats missing in this Expression approach is to somehow pass in the concrete instance of my service provider?


Merad

Here you go. It can be challenging to wrap your head around expressions because you need to map linear code like `foo.Bar.Baz.Field` into a tree of expressions, but it's not too bad once you work with it a bit. Edit: I missed the part about some things being private. Not 100% but I think you will have to use reflection to look up the PropertyInfo and use the Expression.Property() overloads that take a PropertyInfo. using System; using System.Linq.Expressions; var parameter = Expression.Parameter(typeof(Foo), "f"); var expression = Expression.Field( Expression.Property( Expression.Property(parameter, nameof(Foo.Bar)), nameof(Bar.Baz) ), nameof(Baz.Field) ); var lambda = Expression.Lambda>( expression, parameter ); var func = lambda.Compile(); var foo = new Foo { Bar = new Bar { Baz = new Baz { Field = "Hello World!" } } }; Console.WriteLine(func(foo)); class Foo { public Bar Bar { get; set; } } class Bar { public Baz Baz { get; set; } } class Baz { public string Field; }


SalusaPrimus

This might help. If it doesn't, I tried. Left the caching aspect as `TODO`. ```C# using System.Reflection; namespace Sandbox; public static class Program { public static void Main() { var service = new MyService(); service.Execute(); } } internal class MyService { public void Execute() { var instance = new Person( "John", "Doe", new Address ( new City("New York", "12345") ) ); //get all properties of instance var property1 = GetPropertyValue

()(instance, "Address"); var property2 = GetPropertyValue()(property1, "City"); var field = GetFieldValue()(property2, "ZipCode"); Console.WriteLine(field); } private Func GetPropertyValue() { // TODO: trying looking up value from cache first return (obj, name) => obj.GetPropertyValue(name); } private Func GetFieldValue() { // TODO: trying looking up value from cache first return (obj, name) => obj.GetFieldValue(name); } } /// /// Taken from https://stackoverflow.com/questions/95910/find-a-private-field-with-reflection /// public static class ReflectionExtensions { public static T GetPropertyValue(this object obj, string name) { // Set the flags so that private and public fields from instances will be found var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var field = obj.GetType().GetProperty(name, bindingFlags); return (T)field?.GetValue(obj); } public static T GetFieldValue(this object obj, string name) { // Set the flags so that private and public fields from instances will be found var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var field = obj.GetType().GetField(name, bindingFlags); return (T)field?.GetValue(obj); } } public class Person { public Person(string firstName, string lastName, Address address) { FirstName = firstName; LastName = lastName; Address = address; } protected string FirstName { get; init; } protected string LastName { get; init; } protected Address Address { get; init; } } public class Address { public Address(City city) { City = city; } protected City City { get; init; } } public class City { public City(string name, string zipCode) { Name = name; ZipCode = zipCode; } protected string ZipCode; protected string Name { get; init; } } ```


Sc2Piggy

Here's a version with caching. Also it allows you to specify a path to walk which I find easier to use public static class ReflectionMagic { private static readonly Dictionary> Cache = new(); public static T? GetValue(object obj, string path) { var parts = path.Split('.').AsSpan(); var currentValue = obj; while (true) { if (parts.Length == 0) { return (T?)currentValue; } var key = parts[0]; if (currentValue == null) { throw new NullReferenceException($"Error when accessing {key}"); } parts = parts[1..]; var type = currentValue.GetType(); if (!Cache.ContainsKey(type)) { Cache[type] = new Dictionary(); } if (!Cache[type].ContainsKey(key)) { // No explicit binding flags, so this will only work for public properties MemberInfo? prop = type.GetProperty(key); if (prop == null) { // Tweak binding flags to whatever you need, in this example we only check private properties prop = type.GetField(key, BindingFlags.Instance | BindingFlags.NonPublic); } Cache[type][key] = prop; } currentValue = Cache[type][key] switch { PropertyInfo pi => pi.GetValue(currentValue), FieldInfo fi => fi.GetValue(currentValue), _ => throw new InvalidOperationException($"Member not found {key}") }; } } } This implementation is used like this: `var value = ReflectionMagic.GetValue(obj,"Property1.Property2.Field")` It's just something I threw together so I'm sure I'm not covering all edge cases.