We've recently hit some performance issues by excessive/improper use of some reflection API's in our application. I've solved one of the issues which had to do with GetCustomAttribute<T>
by caching some stuff. But I'm struggling with improving the performance of another part of our code which uses MethodInfo.Invoke(...)
.
To explain the whole situation here's some stuff for context. We have a generic interface we use for all our mappers:
public interface IMapper<in TIn, out TOut>
{
TOut Map(TIn instance);
}
And we have lots of implementations of this interface, like this example:
public class SomeEntityMapper : IMapper<SomeObject, SomeEntity>
{
public SomeEntity Map(SomeObject instance)
{
//Create an instance of SomeEntity and map stuff from the SomeObject instance
}
}
All the objects that get mapped share an interface:
[ACustomAttribute("somestring")]
public class ConcreteEntity : IEntity
{
// Properties
}
We have a class that has to find the correct mapper, get the instance from IServiceProvider
and invoke the Map
method and return the result:
public class MapHelper : IMapHelper
{
private readonly IServiceProvider serviceProvider;
private static readonly IEnumerable<Type> types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).ToList();
private static readonly ConcurrentDictionary<string, Type> entityTypeCache = new ConcurrentDictionary<string, Type>();
private static readonly ConcurrentDictionary<Type, MethodInfo> methodInfoCache = new ConcurrentDictionary<Type, MethodInfo>();
public TridionComponentHelper(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
public IEntity GetEntity(SomeObject instance)
{
if (instance == null) throw new ArgumentNullException(nameof(instance));
Type entityType = GetEntityTypeByName(instance.Name);
Type mapperType = types.Single(t => t.IsClass && t.Name == $"{entityType.Name}Mapper");
Type mapperInterfaceType = mapperType.GetInterfaces().Single(i => i.Name.Contains("IMapper"));
var mapperInstance = serviceProvider.GetRequiredService(mapperInterfaceType);
MethodInfo mapMethod = GetMethodInfo(mapperInstance.GetType());
var entity = mapMethod.Invoke(mapperInstance, new[] { instance } ) as IEntity;
return entity;
}
private static Type GetEntityTypeByName(string name)
{
if (entityTypeCache.TryGetValue(name, out Type cachedType))
{
return cachedType;
}
else
{
var type = types.Single(t => t.IsClass && t.GetCustomAttribute<ACustomAttribute>()?.Name == name);
entityTypeCache.TryAdd(name, type);
return type;
}
}
private static MethodInfo GetMethodInfo(Type type)
{
if (methodInfoCache.TryGetValue(type, out MethodInfo cachedMethodInfo))
{
return cachedMethodInfo;
}
else
{
var methodInfo = type.GetMethod("Map");
methodInfoCache.TryAdd(type, methodInfo);
return methodInfo;
}
}
}
We use this MapHelper
in another class where we loop through a list of SomeObject
and return the mapped entities. I do not know beforehand what the type of the mapper is supposed to be or else I could've used serviceProvider.GetRequiredService<T>()
and just called Map
directly. So instead I was using the following code at first to retrieve the right instance of the mapper (registered by the Microsoft.Extensions.DependencyInjection
DI framework) and changed to the code above after I noticed it caused some performance issues:
///GetEntity method
...
var mapperInstance = serviceProvider.GetRequiredService(mapperInterfaceType);
MethodInfo mapMethod = mapperInstance.GetType().GetMethod("Map");
var entity = mapMethod.Invoke(mapperInstance, new[] { instance } ) as IEntity;
...
As you can see in the full implementation of MapHelper, I cache all the MethodInfo
's I retrieve in a ConcurrentDictionary
so any subsequent calls can reuse them. But I haven't noticed any (significant) difference in performance between the code caching the MethodInfo
's and the one that doesn't. Am I doing something wrong?
Aucun commentaire:
Enregistrer un commentaire