Лучший способ динамически создавать классы вместо использования блока переключателей
В настоящее время моя функциональность VaryByCustom реализована в классах, которые реализуют интерфейс IOutputCacheVaryByCustom
public interface IOutputCacheVaryByCustom
{
string CacheKey { get; }
HttpContext Context { get; }
}
Класс, реализующий этот интерфейс, имеет несколько соглашений, имя класса будет "OutputCacheVaryBy_______", где пробел - это значение, которое передается из свойства varByCustom на страницах. Другое соглашение состоит в том, что Context будет установлен через внедрение конструктора.
В настоящее время я основываюсь на перечислении и выражении switch, аналогичном
public override string GetVaryByCustomString(HttpContext context,
string varyByCustomTypeArg)
{
//for a POST request (postback) force to return back a non cached output
if (context.Request.RequestType.Equals("POST"))
{
return "post" + DateTime.Now.Ticks;
}
var varyByCustomType = EnumerationParser.Parse<VaryByCustomType?>
(varyByCustomTypeArg).GetValueOrDefault();
IOutputCacheVaryByCustom varyByCustom;
switch (varyByCustomType)
{
case VaryByCustomType.IsAuthenticated:
varyByCustom = new OutputCacheVaryByIsAuthenticated(context);
break;
case VaryByCustomType.Roles:
varyByCustom = new OutputCacheVaryByRoles(context);
break;
default:
throw new ArgumentOutOfRangeException("varyByCustomTypeArg");
}
return context.Request.Url.Scheme + varyByCustom.CacheKey;
}
Так как я всегда знаю, что класс будет OutputCacheVaryBy + varyByCustomTypeArg
и единственный аргумент конструктора будет context
Я понял, что могу обойти этот прославленный блок if else и просто создать экземпляр своего собственного объекта с помощью Activator
,
С учетом сказанного, рефлексия не моя сильная сторона, и я знаю, что Activator
существенно медленнее по сравнению со статическим созданием и другими способами генерирования объектов. Есть ли какая-то причина, почему я должен придерживаться этого текущего кода или я должен использовать Activator
или похожий способ создания моего объекта?
Я видел блог http://www.smelser.net/blog/post/2010/03/05/When-Activator-is-just-to-slow.aspx но я не совсем уверен, как это будет применяться так как я работаю с типами во время выполнения, а не статический Т.
6 ответов
Вам на самом деле не нужно использовать отражение, поскольку это довольно ограниченный набор возможных значений. Однако вы можете сделать что-то вроде этого
internal class Factory<T,Arg>
{
Dictionary<string,Func<Arg.T>> _creators;
public Factory(IDictionary<string,Func<Arg,T>> creators)
{
_creators = creators;
}
}
и замените свою логику создания
_factory[varyByCustomTypeArg](context);
это не так быстро, как переключатель, но он сохраняет конструкцию и использовать отдельно
Если Reflection слишком медленный для вас. Вы можете, вероятно, получить свой собственный ObjectFactory работать. Это действительно легко. Просто добавьте новый метод в ваш интерфейс.
public interface IOutputCacheVaryByCustom
{
string CacheKey { get; }
IOutputCacheVaryByCustom NewObject();
}
Чем создать статический только для чтения CloneDictionary, который содержит шаблоны объектов.
static readonly
Dictionary<VaryByCustomType, IOutputCacheVaryByCustom> cloneDictionary
= new Dictionary<VaryByCustomType, IOutputCacheVaryByCustom>
{
{VaryByCustomType.IsAuthenticated, new OutputCacheVaryByIsAuthenticated{}},
{VaryByCustomType.Roles, new OutputCacheVaryByRoles{}},
};
Если вы закончили с этим, вы можете использовать перечисление, которое у вас уже есть, чтобы выбрать шаблон в словаре и вызвать NewObject()
IOutputCacheVaryByCustom result =
cloneDictionary[VaryByCustomType.IsAuthenticated].NewObject();
Это так просто. Метод NewObject(), который вы должны реализовать, вернет новый экземпляр, непосредственно создав объект.
public class OutputCacheVaryByIsAuthenticated: IOutputCacheVaryByCustom
{
public IOutputCacheVaryByCustom NewObject()
{
return new OutputCacheVaryByIsAuthenticated();
}
}
Это все, что вам нужно иметь. И это невероятно быстро.
Мне действительно нравится, когда о создании объекта заботится кто-то другой. Например, если мне нужны разные конкретные реализации интерфейса, контейнер IoC творил чудеса для меня.
В качестве простого примера, использующего Unity, у вас есть часть конфигурации, связывающая ключи с реализациями, такими как:
public void Register(IUnityContainer container)
{
container.RegisterType<IOutputCacheVaryByCustom,OutputCacheVaryByIsAuthenticated>("auth");
container.RegisterType<IOutputCacheVaryByCustom,OutputCacheVaryByRoles>("roles");
}
и твое творение выглядело бы намного проще так:
//injected in some form
private readonly IUnityContainer _container;
public override string GetVaryByCustomString(HttpContext context,
string varyByCustomTypeArg)
{
//for a POST request (postback) force to return back a non cached output
if (context.Request.RequestType.Equals("POST"))
{
return "post" + DateTime.Now.Ticks;
}
try
{
IOutputCacheVaryByCustom varyByCustom = _container.Resolve<IOutputCacheVaryByCustom>(varyByCustomTypeArg, new DependencyOverride<HttpContext>(context));
}
catch(Exception exc)
{
throw new ArgumentOutOfRangeException("varyByCustomTypeArg", exc);
}
return context.Request.Url.Scheme + varyByCustom.CacheKey;
}
Или, если IoC не вариант, я бы позволил фабрике создавать конкретные классы, так что вам никогда не придется беспокоиться о ваших реальных методах.
Оставайтесь с заявлением выключателя. Если у вас есть только несколько возможных случаев, подобных этому, то вы просто пытаетесь использовать умную абстракцию, чтобы не сидеть сложа руки и заставить трудные части вашей программы работать...
Тем не менее, из вашего вопроса, кажется, используя Activator
может работать на тебя. Вы проверяли это? Было ли это слишком медленно?
В качестве альтернативы, вы можете просто сохранить несколько заводских методов в Dictionary<string, Func<IOutputCacheVaryByCustom>
, Это я бы использовал, если вы часто создаете эти объекты (в цикле). Вы можете также оптимизировать string
ключ к вашему enum
и будет сделано с преобразованием. Если вы будете более абстрактными, то просто скрываете смысл этого куска кода...
Вот пример для создания нового объекта
public static object OBJRet(Type vClasseType)
{
return typeof(cFunctions).GetMethod("ObjectReturner2").MakeGenericMethod(vClasseType).Invoke(null, new object[] { });
}
public static object ObjectReturner2<T>() where T : new()
{
return new T();
}
Немного информации:
- cFunctions - это имя моего статического класса, содержащего функции
Также пример, где я получаю класс, содержащийся в массиве:
public static object OBJRet(Type vClasseType, ArrayList tArray, int vIndex)
{
return typeof(cFunctions).GetMethod("ObjectReturner").MakeGenericMethod(vClasseType).Invoke(null, new object[] { tArray, vIndex });
}
public static object ObjectReturner<T>(ArrayList tArray, int vIndex) where T : new()
{
return tArray[vIndex];
}
Используйте отражение.
public override string GetVaryByCustomString(HttpContext context,
string varyByCustomTypeArg)
{
//for a POST request (postback) force to return back a non cached output
if (context.Request.RequestType.Equals("POST"))
{
return "post" + DateTime.Now.Ticks;
}
Type type = Type.GetType("OutputCacheVaryBy" + varyByCustomTypeArg, false)
if (type == null)
{
Console.WriteLine("Failed to find a cache of type " + varyByCustomTypeArg);
return null;
}
var cache = (IOutputCacheVaryByCustom)Activator.CreateInstance(type, new object[]{context});
return context.Request.Url.Scheme + cache.CacheKey;
}
Возможно, вам придется добавить префикс typename к пространству имен: "My.Name.Space.OutputCacheVaryBy". Если это не сработает, попробуйте указать имя, полное сборки:
Type.GetType("Name.Space.OutputCacheVaryBy" + varyByCustomTypeArg + ", AssemblyName", false)