简体   繁体   中英

How can I create a delegate to read a property of an anonymous type?

I have a method that is called with an instance of an anonymous type. The type is always the same, but the instance is different.

NOTE : I am passed the anonymous object simply as a type object .

I know the anonymous type has a property named Request of type HttpRequestMessage . Here is my method that is executed with the anonymous type in info.Value .

void IObserver<KeyValuePair<string, object> event> OnNext(KeyValuePair<string, object> info)
{
  HttpRequestMessage request = ?????
}

I can get the property getter like so:

MethodInfo propertyGetter = type.GetProperty("Request").GetGetMethod();

But I can't afford the cost of reflection when reading the property of the instance passed to me.

How can I create a delegate that I pass the instance to and get the property value?

I tried this

private delegate HttpRequestMessage RequestPropertyGetterDelegate(object instance);
private static RequestPropertyGetterDelegate RequestPropertyGetter;

private static RequestPropertyGetterDelegate CreateRequestFromPropertyDelegate(Type type)
{
    MethodInfo propertyGetter = type.GetProperty("Request").GetGetMethod();
    return (RequestPropertyGetterDelegate)Delegate.CreateDelegate(typeof(RequestPropertyGetterDelegate), propertyGetter);
}

But I am experiencing a binding error

System.ArgumentException: 'Cannot bind to the target method because its signature is not compatible with that of the delegate type.'

Here it is, using expression trees.
All you have to do is cache the getter , the performance should be the same as direct access.

void Main()
{
    var instance = new TestClass { Request = "Something 2" };
    var getter = CreateRequestFromPropertyDelegate(typeof(TestClass));
    // Cache getter per type
    var value = getter(instance);
    Console.WriteLine(value); // Prints "Something 2"
}

private delegate string RequestPropertyGetterDelegate(object instance);

static RequestPropertyGetterDelegate CreateRequestFromPropertyDelegate(Type type)
{
    // Entry of the delegate
    var instanceParam = Expression.Parameter(typeof(object), "instance");
    
    // Cast the instance from "object" to the correct type
    var instanceExpr = Expression.TypeAs(instanceParam, type);
    
    // Get the property's value
    var property = type.GetProperty("Request");
    var propertyExpr = Expression.Property(instanceExpr, property);
    
    // Create delegate
    var lambda = Expression.Lambda<RequestPropertyGetterDelegate>(propertyExpr, instanceParam);
    return lambda.Compile();
}

class TestClass
{
    // Using string here because I'm on LINQPad
    public string Request { get; set; }
}

Using some expression trees it should be:

private static readonly ConcurrentDictionary<Type, Func<object, string>> extractorsCache = new ConcurrentDictionary<Type, Func<object, string>>();

public static string GetRequest(object obj)
{
    Type type = obj.GetType();

    Func<object, string> extractor = extractorsCache.GetOrAdd(type, BuildExtractor);

    string res = extractor(obj);

    return res;
}

public static Func<object, string> BuildExtractor(Type type)
{
    var par = Expression.Parameter(typeof(object));
    var prop = Expression.Property(Expression.TypeAs(par, type), "Request");
    return Expression.Lambda<Func<object, string>>(prop, par).Compile();
}

and then:

string r1 = GetRequest(new { Request = "Foo" });
string r2 = GetRequest(new { Request = "Bar" });
string r3 = GetRequest(new { Request = "Baz" });
string r4 = GetRequest(new { Request = "Zoo", Ix = 1 });

Note that the compiled expression trees are cached in a ConcurrentDictionary<,> , so these four GetRequest will generate two compiled expressions (because in the end there are two anonymous types here).

There is no way to do this without Genric-ising the function you are writing it in. Because the anonymous type is more specific than object , an object parameter will never bind.

You can work around this by putting the whole thing in a static generic class:

static class AnonHelper<T>
{
    public readonly Func<T, HttpRequestMessage> Getter = (Func<T, HttpRequestMessage>)
        Delegate.CreateDelegate(
        typeof(Func<T, HttpRequestMessage>, propertyGetter)),
        typeof(T)
        .GetProperty("Request")
        .GetGetMethod()
        );
        
}

You still cannot access it as you cannot declare the generic parameter.

So wrap it in a function on the outside (you could even make it an extension):

HttpRequestMessage GetProp<T>(T obj)
{
    return AnonHelper<T>.Getter(obj);
}   

You can then call it like so:

GetProp(anonObj)

This only works if you can call GetProp at the point where the type of the anonymous object is statically known, usually the same method it is declared in.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM