Пользовательский атрибут выполняется во время компиляции - PullRequest
4 голосов
/ 30 августа 2011

Я пытаюсь создать собственный атрибут, который будет работать в некотором роде AOP-способом (к сожалению, у меня нет доступа к postsharp, и я не очень знаком с Unity). Он имеет AttributeUsage.Method и в своем конструкторе настраивает некоторые части тестовой среды (извлекает некоторую информацию из app.config и вызывает некоторые exes, которые конфигурируют среду).

Это работает, за исключением того, что прямо сейчас, когда я строю решение, выполняется атрибут - что нежелательно.

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

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 ]

6 голосов
/ 30 августа 2011

Обычный метод - использовать атрибут строго как маркер. Вы настраиваете его в конструкторе, но не предпринимаете никаких действий. Затем во время выполнения вы проверяете методы с помощью отражения, извлекаете информацию о конфигурации из атрибута и выполняете соответствующие действия на основе этой информации.

Например, если вам нужен атрибут, проверяющий параметры на 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
}

Обновление : поскольку это необходимо в сценарии, где MSTest запускает метод, вам следует взглянуть на эту статью , в которой обсуждается, как подключиться к процессу тестирования. По сути, вам нужно перейти от ContextBoundObject и перехватить вызовы методов, чтобы выполнить необходимую обработку атрибутов.

Обновление 2 : Учитывая, что делает атрибут, я бы, вероятно, просто настроил метод среды и вызвал его с начала соответствующих тестов. Я не думаю, что ты получаешь так много, имея атрибут. В качестве альтернативы вы можете разделить свои приборы по среде, и выполнить настройку / разбор среды в настройке / разборке прибора. В любом случае, вероятно, намного проще, чем пытаться заставить AOP работать здесь, , особенно , учитывая природу атрибута "сделай это один раз".

1 голос
/ 12 июля 2018

Я работал над атрибутами и хотел ограничить наследование во время компиляции. Это не сработало, однако я нашел решение для времени выполнения. Я держал это как компактный, но законченный насколько возможно, так что вот код для любого, кто, оказывается, заинтересован. Использование простое: вы добавляете атрибут с заданными типами, которым разрешено наследовать от него.

Атрибут:

[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 раньше), пользовательское исключение будет выдано, чтобы указать, что произошло запрещенное наследование. Лично я мог бы использовать это, чтобы запретить моим коллегам наследовать от классов, которые не должны наследоваться из-за того, что они предназначены для низкоуровневого кода, который является основой всего рабочего проекта.

В любом случае этот код может дать представление о рабочем примере не слишком простого атрибута. Больше всего я нашел только атрибуты, содержащие некоторые элементы, которые затем вызывались практически без контекста.

Редактирование: для пользователей Unity - найдите атрибут UnityEditor.Callbacks.DidReloadScripts в методе static , чтобы вы могли запускать сценарий после компиляции, а не во время выполнения, это то, что я лично хотел иметь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...