我正在尝试创建一个自定义属性,它将以一种AOP方式工作(我无法访问postharp,不幸的是,我对Unity不是很熟悉)。它有AttributeUsage.Method,并在其构造函数中配置测试环境的某些部分(从app.config中提取一些信息并调用一些配置环境的exes)。
它的工作原理,除了现在,当我构建解决方案时,属性被执行 - 这是不可取的。
有没有办法创建一个未在编译时执行的自定义属性?
编辑>我想一个示例用法可能会有所帮助:
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)
{
}
}
答案 0 :(得分:5)
通常的方法是严格使用属性作为标记。您在构造函数中配置它,但不采取任何操作。然后,在运行时,您通过反射检查方法,从属性中提取配置信息,并根据该信息采取适当的操作。
例如,如果您想要在执行方法之前检查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运行该方法的场景中需要这个,因此您应该查看this article,其中讨论了如何挂钩测试过程。基本上,您需要从ContextBoundObject
扩展,并拦截方法调用以执行所需的属性处理。
更新2 :鉴于该属性的作用,我可能只是将环境设置为一个方法,并从相应测试的开头调用它。我不认为你通过拥有一个属性获得了那么多。或者,您可以按环境划分灯具,并在夹具设置/拆卸中执行环境设置/拆卸。无论哪种方式都可能比尝试使AOP在这里工作容易得多,特别是给出属性的“一次性”性质。
答案 1 :(得分:1)
我正在研究属性,并希望在编译时限制继承。那没有用,但是我找到了运行时的解决方案。我保持它的紧凑性,但尽可能地使它完成,因此这里是适合任何有兴趣的人的代码。用法很简单:您可以添加具有允许从其继承的给定类型的属性。
属性:
[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属性,这样您就可以在编译后而不是运行时运行脚本,这是我个人的想法想拥有。