Как я могу создать делегат для чтения свойства анонимного типа?

#c# #reflection

#c# #отражение

Вопрос:

У меня есть метод, который вызывается с экземпляром анонимного типа. Тип всегда один и тот же, но экземпляр отличается.

ПРИМЕЧАНИЕ: мне передается анонимный объект просто как тип object .

Я знаю, что анонимный тип имеет свойство с именем Request типа HttpRequestMessage . Вот мой метод, который выполняется с анонимным типом in info.Value .

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

Я могу получить средство получения свойства следующим образом:

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

Но я не могу позволить себе затраты на размышления при чтении свойства переданного мне экземпляра.

Как я могу создать делегата, которому я передаю экземпляр и получаю значение свойства?

Я попробовал это

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

Но я испытываю ошибку привязки

Система.Исключение ArgumentException: «Невозможно выполнить привязку к целевому методу, поскольку его сигнатура несовместима с сигнатурой типа делегата».

Комментарии:

1. Вы «не можете позволить себе расходы на отражение», но продолжаете создавать фрагмент кода, который использует как отражение, так и делегаты? Если тип тот же, почему вы просто не кэшируете MethodInfo? dotnetfiddle.net/GecXYf

2. В params данном случае это не имеет смысла, и object instance параметр в любом случае приведет к сбою.

3. Если вы создаете делегат из MethodInfo, он не использует отражение. Таким образом, вы можете отразить один раз, чтобы найти MethodInfo, а затем избегать отражения с этого момента.

4. Вы не можете, потому что тип неизвестен. Есть обходной путь, опубликую

5. Деревья выражений будут выполнять эту работу и будут намного эффективнее.

Ответ №1:

Вот оно, использование деревьев выражений.
Все, что вам нужно сделать, это кэшировать getter , производительность должна быть такой же, как у прямого доступа.

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

Ответ №2:

Используя некоторые деревья выражений, это должно быть:

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

и затем:

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

Обратите внимание, что скомпилированные деревья выражений кэшируются в a ConcurrentDictionary<,> , поэтому эти четыре GetRequest будут генерировать два скомпилированных выражения (потому что в итоге здесь два анонимных типа).

Ответ №3:

Невозможно сделать это без генерации функции, в которую вы ее записываете. Поскольку анонимный тип более специфичен, чем object , object параметр никогда не будет привязываться.

Вы можете обойти это, поместив все это в статический универсальный класс:

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

Вы по-прежнему не можете получить к нему доступ, поскольку не можете объявить универсальный параметр.

Поэтому оберните его в функцию снаружи (вы даже можете сделать его расширением):

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

Затем вы можете вызвать его следующим образом:

GetProp(anonObj)

Это работает, только если вы можете вызвать GetProp в точке, где тип анонимного объекта статически известен, обычно тот же метод, в котором он объявлен.

Комментарии:

1. anonObj всегда будет типичным object — так что это мне не поможет, не так ли?

2. Зависит от того, находитесь ли вы все еще в точке, с которой оно объявлено var , там у вас все еще есть доступ к типу

3. Нет, объект передается как быть (обновлен вопрос).

4. Тогда это, очевидно, невозможно. Вы не можете получить доступ к своему кэшу без отражения