Пользовательский атрибут выполняется во время компиляции


Я пытаюсь создать пользовательский атрибут, который будет работать в некотором роде 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 4

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 в статическом методе , таким образом, вы можете запустить сценарий после компиляции, а не во время выполнения, что я лично хотел бы иметь.