如何使用 PostSharp 验证方法或 属性 条目中的对象状态?

How to validate the object state in method or property entry using PostSharp?

如何使用 PostSharp 验证方法条目中的对象状态(示例:布尔字段的值)?

属性 getter 或 setter 也可以吗? 自动属性也可以吗?

我知道如何通过自定义契约验证方法参数以及如何通过 methodboundry 拦截方法,但我不知道如何将状态验证规则从 aspect 属性传递到方法入口主体。

我的用例:

我希望 Method1、2 和 3 中的所有初始化检查都用一个方面来处理。

无方面:

class MyClass
{
   bool Initialized;

   void Init()
   {
       //do stuff;
       Initialized = true;
   }

   void Method1()
   {
       if (!Initialized) throw AwesomeException("Awesome text");

       //do stuff;
   }

   void Method2()
   {
       if (!Initialized) throw AwesomeException("Awesome text");

       //do stuff;
   }

   void Method3()
   {
       if (!Initialized) throw AwesomeException("Awesome text");

       //do stuff;
   }
}

方面:

   [Conditional( <<somehow define condition field here>> Initialized, "Awesome text"]
   void Method1()
   {
       //do stuff;
   }

   [Conditional( <<magic>> Initialized, "Awesome text"]
   void Method2()
   {
       //do stuff;
   }

   [Conditional( <<magic>> Initialized, "Awesome text"]
   void Method3()
   {
       //do stuff;
   }

解决方案需要一些稍微高级的 PostSharp 功能。

您需要能够从您的方面访问 "condition field",并且您需要能够配置哪个字段是 "condition field"。不幸的是,C# 允许您将常量表达式指定为属性参数。唯一的方法(我知道)是使用字符串来指定 "condition field" 名称:

   [Conditional( "Initialized", "Awesome text"]
   void Method1()
   {
       //do stuff;
   }

问题是智能感知不适用于字符串,但您可以在您的方面验证该字段的存在。

您可以使用 ImportLocationAdviceInstance and implementing IAdviceProvider 导入目标 class 的任何字段:

[PSerializable]
public class ConditionalAttribute : InstanceLevelAspect, IAdviceProvider
{
    private string conditionFieldName;
    private string awesomeText;
    public ILocationBinding ConditionBindingField;

    public ConditionalAttribute(string conditionFieldName, string awesomeText)
    {
        this.conditionFieldName = conditionFieldName;
        this.awesomeText = awesomeText;
    }

    public IEnumerable<AdviceInstance> ProvideAdvices(object targetElement)
    {
        var targetType = (Type) targetElement;
        var bindingFieldInfo = this.GetType().GetField("ConditionBindingField", BindingFlags.Instance | BindingFlags.Public);

        foreach (
            var field in
            targetType.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public |
                                 BindingFlags.NonPublic))
        {
            if (field.Name == conditionFieldName)
            {
                if (field.FieldType.IsAssignableFrom(typeof(bool)))
                {
                    yield return new ImportLocationAdviceInstance(bindingFieldInfo, new LocationInfo(field));
                    yield break;
                }
                {
                    Message.Write(MessageLocation.Of(targetType), SeverityType.Error, "ERR002", $"{targetType.Name} contains {field.FieldType.Name} {conditionFieldName}. {conditionFieldName} has to be bool.");
                    yield break;
                }
            }
        }

        Message.Write(MessageLocation.Of(targetType), SeverityType.Error, "ERR001", $"{targetType.Name} doesn't contain {conditionFieldName}");
    }
}

现在,ConditionBindingField 要么包含绑定到 "condition field",要么 PostSharp 发出 ERR001 或 ERR002 如果给定名称的 "condition field" 不存在或声明为其他类型bool.

下一步是拦截目标class方法验证码:

[OnMethodInvokeAdvice]
[MethodPointcut("SelectMethods")]
public void OnInvoke(MethodInterceptionArgs args)
{
    bool conditionFieldValue = (bool)ConditionBindingField.GetValue(args.Instance);
    if (!conditionFieldValue)
    {
        throw new InvalidOperationException(awesomeText);
    }

    args.Proceed(); // call original method body
}

PostSharp 使用OnInvoke 方法中的代码拦截SelectMethods 方法提供的每个方法。问题是你不想用 "condition field" 检查拦截 Init 方法,否则调用这个方法会抛出 "Awesome text" 的异常并且不可能初始化一个class。所以你必须以某种方式标记无法拦截的方法。您可以使用约定并为所有 Init 方法指定相同的名称,或者您可以使用属性标记 Init 方法:

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

您可以使用 MethodPointcut 到 select public 没有 Init 属性的实例方法:

private IEnumerable<MethodInfo> SelectMethods(Type type)
{
    const BindingFlags bindingFlags = BindingFlags.Instance |
        BindingFlags.DeclaredOnly | BindingFlags.Public;

    return type.GetMethods(bindingFlags)
        .Where(m => !m.GetCustomAttributes(typeof(InitAttribute)).Any());
}

条件方面用法示例:

[Conditional("Initialized", "Awesome text")]
class MyClass
{
   bool Initialized;

   [Init]
   void Init()
   {
       Initialized = true;
   }

   void Method1()
   {
       //do stuff;
   }
}

编辑:可以通过属性标记 "condition field" 而不是将其名称指定为字符串:

[Conditional("Awesome text")]
class MyClass
{
   [ConditionField]
   bool Initialized;

   [Init]
   void Init()
   {
       Initialized = true;
   }

   void Method1()
   {
       //do stuff;
   }
}