При использовании рефлексии вы должны сначала задать себе пару вопросов, потому что вы можете оказаться в сложном комплексном решении, которое сложно поддерживать:
- Есть ли способ решитьпроблема с использованием обобщенности или наследования класса / интерфейса?
- Можно ли решить проблему с помощью вызовов
dynamic
(только для .NET 4.0 и выше)? - Важна ли производительность, т. е. будет ли отражен мой методили вызов экземпляра вызывается один, два или миллион раз?
- Могу ли я объединить технологии, чтобы получить умное, но выполнимое / понятное решение?
- Я согласен с потерей безопасности времени компиляции?
Универсальность / динамичность
Из вашего описания я предполагаю, что вы не знаете типы во время компиляции, вы знаете только, что они имеют общий интерфейс ICalculation
.Если это правильно, то цифры (1) и (2), приведенные выше, вероятно, не возможны в вашем сценарии.
Производительность
Это важный вопрос, который нужно задать.Затраты на использование отражения могут помешать штрафу более чем в 400 раз: это замедляет даже умеренное количество вызовов.
Разрешение относительно простое: вместо использования Activator.CreateInstance
используйте фабричный метод (выуже есть), найдите MethodInfo
создайте делегата, кэшируйте его и используйте делегат с этого момента.Это дает только штраф при первом вызове, последующие вызовы имеют почти нативную производительность.
Объединение технологий
Здесь многое возможно, но мне действительно нужно знать больше о вашей ситуациипомогать в этом направлении.Часто я заканчиваю тем, что комбинирую dynamic
с генериками с кэшированным отражением.При использовании сокрытия информации (как обычно в ООП) вы можете получить быстрое, стабильное и все еще хорошо расширяемое решение.
Потеря безопасности при компиляции типа
Из пяти вопросов:это, пожалуй, самый важный вопрос для беспокойства.Очень важно создать свои собственные исключения, которые дают четкую информацию об ошибках отражения.Это означает: каждый вызов метода, конструктора или свойства, основанный на входной строке или другой непроверенной информации, должен быть заключен в try / catch.Поймать только конкретные исключения (как всегда, я имею в виду: никогда не поймать Exception
).
Сосредоточьтесь на TargetException
(метод не существует), TargetInvocationException
(метод существует, но при вызове был исключен), TargetParameterCountException
, MethodAccessException
(не нужные привилегии, часто случаетсяв ASP.NET) InvalidOperationException
(происходит с универсальными типами).Вам не всегда нужно пытаться поймать их все, это зависит от ожидаемого ввода и ожидаемых целевых объектов.
Чтобы подвести итог
Избавьтесь от Activator.CreateInstance
и используйтеMethodInfo, чтобы найти метод factory-create, и используйте Delegate.CreateDelegate
для создания и кэширования делегата.Просто сохраните его в статическом Dictionary
, где ключ равен строке класса в вашем примере кода.Ниже приведен быстрый, но не очень грязный способ сделать это безопасно и без потери безопасности.
Пример кода
public class TestDynamicFactory
{
// static storage
private static Dictionary<string, Func<ICalculate>> InstanceCreateCache = new Dictionary<string, Func<ICalculate>>();
// how to invoke it
static int Main()
{
// invoke it, this is lightning fast and the first-time cache will be arranged
// also, no need to give the full method anymore, just the classname, as we
// use an interface for the rest. Almost full type safety!
ICalculate instanceOfCalculator = this.CreateCachableICalculate("RandomNumber");
int result = instanceOfCalculator.ExecuteCalculation();
}
// searches for the class, initiates it (calls factory method) and returns the instance
// TODO: add a lot of error handling!
ICalculate CreateCachableICalculate(string className)
{
if(!InstanceCreateCache.ContainsKey(className))
{
// get the type (several ways exist, this is an eays one)
Type type = TypeDelegator.GetType("TestDynamicFactory." + className);
// NOTE: this can be tempting, but do NOT use the following, because you cannot
// create a delegate from a ctor and will loose many performance benefits
//ConstructorInfo constructorInfo = type.GetConstructor(Type.EmptyTypes);
// works with public instance/static methods
MethodInfo mi = type.GetMethod("Create");
// the "magic", turn it into a delegate
var createInstanceDelegate = (Func<ICalculate>) Delegate.CreateDelegate(typeof (Func<ICalculate>), mi);
// store for future reference
InstanceCreateCache.Add(className, createInstanceDelegate);
}
return InstanceCreateCache[className].Invoke();
}
}
// example of your ICalculate interface
public interface ICalculate
{
void Initialize();
int ExecuteCalculation();
}
// example of an ICalculate class
public class RandomNumber : ICalculate
{
private static Random _random;
public static RandomNumber Create()
{
var random = new RandomNumber();
random.Initialize();
return random;
}
public void Initialize()
{
_random = new Random(DateTime.Now.Millisecond);
}
public int ExecuteCalculation()
{
return _random.Next();
}
}