Custom Attribute is executed at compile time

旧街凉风 提交于 2021-02-07 18:36:07

问题


I'm trying to create a custom attribute that will work in a sort-of AOP way (I don't have access to postsharp, unfortunately and I'm not very familiar with Unity). It has AttributeUsage.Method and in its constructor it configures some parts of the test environment (pulls some info out of app.config and calls some exes that config the environment).

It works, except that right now, when I build the solution, the attribute is executed - which is undesireable.

Is there a way to create a custom attribute that isn't executed at compile time?

edit> I guess an example usage might help:

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)
    {
    }
}

回答1:


The usual method is to use the attribute strictly as a marker. You configure it in the constructor, but take no actions. Then, at runtime, you inspect the methods via reflection, extract the configuration information from the attribute, and take the appropriate actions based on that information.

For example, if you wanted an attribute that checked parameters for null prior to executing the method, you could create it like so:

[AttributeUsage(AttributeTargets.Method)]
public class CheckArgumentsNullAttribute : Attribute
{
    public CheckArgumentsNullAttribute() { }
}

Then, mark your method as such:

[CheckArgumentsNull]
public Foo(object o) { Console.WriteLine(o.ToString()); }

Then, in your code, get the Foo method via reflection, and check it for the attribute:

MethodInfo m = typeof(FooClass).GetMethod("Foo");
if (m.GetCustomAttributes(typeof(CheckArgumentsNullAttribute), false).Length > 0)
{
    // Check parameters for null here
}

Update: Since you need this in a scenario where MSTest is running the method, you should take a look at this article, which discusses how to hook into the testing process. Basically, you need to extended from ContextBoundObject, and intercept the method calls to perform the attribute processing you want.

Update 2: Given what the attribute does, I would probably just make the environment setup a method, and call it from the beginning of the appropriate tests. I don't think you gain all that much by having an attribute. Alternatively, you could divide your fixtures by environment, and have the environment setup/teardown performed in the fixture setup/teardown. Either way is probably a lot easier than trying to make AOP work here, especially given the "do it once" nature of the attribute.




回答2:


I was working on attributes, and wanted to restrict inheritance at compile time. That didn't work, however I found a solution for run-time. I kept it as compact but finished as possible, so here is the code for anybody who happens to be interested. Usage is simple: You add the attribute with the given types which are allowed to inherit from it.

The attribute:

[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);
    }
}

A custom exception:

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) { }
}

Now, one way is to have lines of codes implemented into the constructor of the class which has the attribute, but that is not an eloquent way to solve it. What you can do instead is to run the following method early in runtime, so that you can run the checks and throw an exception (or just write a plain message).

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;
    }
}

Usage:

//[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 { }

When you start your program (and ideally run the RunAttributes method early), the custom exception will be thrown to indicate that a forbidden inheritance occurred. I personally might use this to prevent my colleagues to inherit from classes which are not supposed to be inherited due to being intended for low-level code which is the basis of the entire work project.

Either way this code might yield a glimpse to a working example of a not-too-simple attribute. The most I found were just attributes containing some members which then were called with barely any context.

Edit: For Unity users - look up for UnityEditor.Callbacks.DidReloadScripts attribute on a static method, that way you can run the script after compilation rather than run-time, which is what I personally wanted to have.



来源:https://stackoverflow.com/questions/7247834/custom-attribute-is-executed-at-compile-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!