【问题标题】:Custom Attribute is executed at compile time自定义属性在编译时执行
【发布时间】:2011-08-30 18:14:46
【问题描述】:

我正在尝试创建一个可以以某种 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)
    {
    }
}

【问题讨论】:

  • 您确定属性的构造函数在编译期间运行吗?如果是这样,那么我不知道为什么 Visual Studio 会费心地唠叨我自定义构建步骤中潜在的恶意代码......
  • @Frédéric Hamidi:很确定。我没有任何构建事件,它调用的 exe 是我代码中的那些,如果我在测试中注释掉该属性,它就不会发生。
  • 所以这是一个测试程序集?它确实应该在构建期间运行:)
  • 我猜当代码在运行的应用程序中加载时,属性的构造函数会被调用,对吧?实际上不是在项目构建的时候?
  • @StriplingWarrior,该属性用于测试项目,计划在构建期间运行。因此,代码确实是在编译后加载到正在运行的应用程序中的。

标签: c# aop custom-attributes


【解决方案1】:

通常的方法是严格使用属性作为标记。您在构造函数中配置它,但不执行任何操作。然后,在运行时,您通过反射检查方法,从属性中提取配置信息,并根据该信息采取适当的行动。

例如,如果您想要一个在执行方法之前检查参数是否为空的属性,您可以这样创建它:

[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 正在运行该方法的情况下需要此功能,因此您应该查看this article,其中讨论了如何挂钩到测试过程。基本上,你需要从ContextBoundObject扩展,并拦截方法调用,进行你想要的属性处理。

更新 2:鉴于该属性的作用,我可能只是让环境设置一个方法,并从相应测试的开头调用它。我不认为你通过拥有一个属性获得了那么多。或者,您可以按环境划分夹具,并在夹具设置/拆卸中执行环境设置/拆卸。无论哪种方式都可能比尝试让 AOP 在这里工作容易得多,尤其是考虑到属性的“只做一次”性质。

【讨论】:

  • 是的,问题在于它是一个测试用例,因此由运行程序(mstest 或实验室环境中的测试代理)执行。我的代码都没有调用该属性。除非你知道另一种方式?
  • 我已经发布了一个编辑,其中包含一些代码示例,如果有帮助的话。
  • 提供的链接很有帮助。谢谢你。根据您的第二次更新。这里有一些限制我的选择的要求(使用夹具设置/拆卸 - 这是我最初的工作,但不足以满足要求)。
【解决方案2】:

我正在处理属性,并希望在编译时限制继承。那没有用,但是我找到了运行时的解决方案。我保持它尽可能紧凑但尽可能完成,所以这里是任何碰巧感兴趣的人的代码。用法很简单:添加具有允许继承的给定类型的属性。

属性:

[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 用户 - 在 static 方法上查找 UnityEditor.Callbacks.DidReloadScripts 属性,这样您就可以在编译后而不是运行时运行脚本,这是我个人想要的有。

【讨论】:

    猜你喜欢
    • 2023-03-05
    • 2013-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-19
    • 2015-12-31
    相关资源
    最近更新 更多