#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. Тогда это, очевидно, невозможно. Вы не можете получить доступ к своему кэшу без отражения