Пользовательский атрибут выполняется во время компиляции
Я пытаюсь создать пользовательский атрибут, который будет работать в некотором роде AOP (у меня нет доступа к postsharp, к сожалению, и я не очень хорошо знаком с Unity). У него есть AttributeUsage.Метод и в его конструкторе он настраивает некоторые части тестовой среды (извлекает некоторую информацию из приложения.config и вызывает некоторых бывших, которые настраивают среду).
Это работает, за исключением того, что прямо сейчас, когда я строю решение, атрибут выполняется - что нежелательно.
Является есть способ создать пользовательский атрибут, который не выполняется во время компиляции?
Edit> я думаю, что пример использования может помочь:
public void Scenario1Tests
{
[Test]
[Environment(Environments.A)]
public void Scenario1TestA()
{
Assert.Something();
}
[Test]
[Environment(Environments.Any)]
public void Scenario1TestB()
{
Assert.SomethingElse();
}
}
// Most tests will be written environment independent, some must not
public enum Environments
{
A,
B,
Any
};
[AtrributeUsage(AttributeTargets.Method)]
public void Environment : Attribute
{
public Environment(Environments env)
{
// lots of test can have this attribute, requirement is
// that it is only configured once as it is a lengthy configuration
if (this.EnvironmentIsAlreadyConfigured())
return;
this.GetSettingsFromAppConfig();
Process.Start(/* ... */); // can take 20 mins+
}
public Environment()
: this(Environments.Any)
{
}
}
2 ответа:
Обычный метод заключается в использовании атрибута строго в качестве маркера. Вы настраиваете его в конструкторе, но не предпринимаете никаких действий. Затем во время выполнения вы проверяете методы с помощью отражения, извлекаете информацию о конфигурации из атрибута и выполняете соответствующие действия на основе этой информации.
Например, если вам нужен атрибут, проверяющий параметры на null перед выполнением метода, вы можете создать его следующим образом:
[AttributeUsage(AttributeTargets.Method)] public class CheckArgumentsNullAttribute : Attribute { public CheckArgumentsNullAttribute() { } }
Затем отметьте свой метод как например:
[CheckArgumentsNull] public Foo(object o) { Console.WriteLine(o.ToString()); }
Затем в коде получите метод
Foo
через отражение и проверьте его на наличие атрибута:MethodInfo m = typeof(FooClass).GetMethod("Foo"); if (m.GetCustomAttributes(typeof(CheckArgumentsNullAttribute), false).Length > 0) { // Check parameters for null here }
Update : поскольку это необходимо в сценарии, где MSTest запускает метод, вы должны взглянуть на эту статью, в которой обсуждается, как подключиться к процессу тестирования. В принципе, вам нужно расшириться от
ContextBoundObject
и перехватить вызовы метода, чтобы выполнить обработку атрибутов, которую вы хотите.Update 2 : учитывая, что атрибут делает, я бы, вероятно, просто сделал установку среды методом и вызывал его с начала соответствующих тестов. Я не думаю, что вы получаете так много, имея атрибут. Кроме того, вы можете разделить светильники по окружению и выполнить настройку/демонтаж среды в окне Настройка/демонтаж светильника. В любом случае, вероятно, намного проще, чем пытаться заставить AOP работать здесь, особенно , учитывая природу атрибута "сделай это один раз".
Я работал над атрибутами и хотел ограничить наследование во время компиляции. Это не сработало, однако я нашел решение для времени выполнения. Я сохранил его как можно более компактным, но законченным, так что вот код для всех, кто случайно заинтересуется. Использование простое: вы добавляете атрибут с заданными типами, которые разрешено наследовать от него.
Атрибут:
[AttributeUsage (AttributeTargets.Class, Inherited = false, AllowMultiple = false)] sealed class RestrictedInheritabilityAttribute : Attribute { public List<Type> AllowedTypes = new List<Type> (); public RestrictedInheritabilityAttribute (Type allowed) { AllowedTypes.Add (allowed); } public RestrictedInheritabilityAttribute (params Type[] allowed) { AllowedTypes.AddRange (allowed); } public bool CheckForException (Type primary) { foreach (Type t in AllowedTypes) { if (primary == t) return true; } throw new RestrictedInheritanceException (primary); } }
Пользовательское исключение:
public class RestrictedInheritanceException : ApplicationException { public RestrictedInheritanceException (Type t) : base ("The inheritance of '" + t.BaseType.FullName + "' is restricted. " + t.FullName + " may not inherit from it!" + Environment.NewLine) { } }
Теперь один из способов состоит в том, чтобы строки кодов были реализованы в конструктор класса, который имеет атрибут, но это не красноречивый способ его решения. Вместо этого можно запустить следующий метод на ранней стадии выполнения, чтобы можно было выполнить проверки и создать исключение (или просто написать простое сообщение).
void RunAttributes () { try { Type[] allTypes = Assembly.GetExecutingAssembly ().GetTypes (); foreach (Type t in allTypes) { Type baseType = t.BaseType; if (baseType != null && baseType != typeof (object)) { var a = baseType.GetCustomAttribute<RestrictedInheritabilityAttribute> (); if (a != null) a.CheckForException (t); } } } catch (Exception e) { throw e; } }
Использование:
//[RestrictedInheritability (typeof (MidClass1), typeof (MidClass2))] [RestrictedInheritability (typeof (MidClass1))] class BaseClass { } class MidClass1 : BaseClass { } // Is fine to go. class MidClass2 : BaseClass { } // Will throw exception. // These are not checked, unless you set 'Inherited = true', then you have to // declare every single class inheriting from the 'root' which has the attribute. class SubClass1 : MidClass1 { } class SubClass2 : MidClass2 { }
При запуске программы (и, в идеале, раннем запуске метода RunAttributes) пользовательское исключение будет выдано, чтобы указать, что произошло запрещенное наследование. Я лично мог бы использовать это, чтобы не позволяйте моим коллегам наследовать от классов, которые не должны наследоваться из-за того, что предназначены для низкоуровневого кода, который является основой всего рабочего проекта.
В любом случае этот код может дать представление о рабочем примере не слишком простого атрибута. Самое большее, что я нашел, были просто атрибуты, содержащие некоторые члены, которые затем вызывались практически без контекста.
Edit: для пользователей Unity-найдите UnityEditor.Обратный звонок.Атрибут DidReloadScripts в статическом методе , таким образом, вы можете запустить сценарий после компиляции, а не во время выполнения, что я лично хотел бы иметь.