带有后锐的自定义类型参考规则

本文关键字:类型 参考 规则 自定义 | 更新日期: 2023-09-27 17:56:26

我刚刚开始评估PostSharp Ultimate,我想在程序集中强制实施一些自定义架构约束。

程序集的结构如下:(基本上对于每个命名空间都有一个根接口和该接口的特定实现)

MycompanyNamespace.Core.CommandDispatcher
  ICommandDispatcher
    XCommandDispatcher
    YCommandDispatcher
    ...
MycompanyNamespace.Core.Services
  IService
    XService
    YService
    ...
MycompanyNamespace.Core.Provider
  IProvider
    XProvider
    YProvider
    ...

我要执行的规则:

  • 不允许上游引用,例如,声明IProvider接口的命名空间中的类型不允许引用声明IServiceICommandDispatcher类型的命名空间中的类型
  • 允许下游引用

我已经尝试了PostSharp附带的ComponentInteral约束,并创建了一个自定义ReferentialConstraint

我不确定

  • 使用正面或负面规则哪个更好?例如

    [GrantAccess(TargetNamespace = typeof(IProvider), GrantedBy = typeof(ICommandDispatcher), typeof(IService)]

    [ProhibitAccess(TargetNamespace = typeof(ICommandDispatcher), Prohibition By = typeof(IProvider), typeof(IService)]

  • 我可以将规则放入全局.cs文件中,用于特定的还是我需要用属性装饰类型?

  • 我可以重复使用预发货的规则吗?或者有人将如何实现这样的自定义规则?

带有后锐的自定义类型参考规则

GrantAccess 或 ProhibitAccess

我不会说在GrantAccess和ProhibitAccess方法之间进行选择时,只有一个选项肯定比另一个更好。这取决于设计的细节以及您希望强制实施约束的严格程度。

我想到的问题是:

给定命名空间中的类是公共的还是此程序集的内部的?

如果将来添加新的命名空间/组件怎么办?默认情况下,新命名空间中的类是否应该能够访问此命名空间?还是应该显式授予此访问权限?

假设示例中的所有类都是内部类,并且您希望在它们之间强制实施引用约束。在这种情况下,GrantAccess 为您提供更严格的控制。新添加的命名空间将无法访问现有命名空间,除非您查看设计并显式授予此访问权限。

或者,您可能希望将服务类设为公共,并仅限制它们在提供程序命名空间中的使用。必须为使用服务的每个外部命名空间显式添加 GrantAccess 非常不方便。在这种情况下,限制较少的禁止访问方法可能更好。

这些只是您可以做出判断的示例,但最终取决于您的设计和项目。

程序集或类型级别属性

由于要将约束应用于给定命名空间中的所有类,因此将特性应用于程序集(在 GlobalAspects.cs 中)并在 AttributeTargetTypes 属性中指定命名空间会更方便。

// Grant access to all classes in MycompanyNamespace.Core.Services namespace
[assembly: GrantAccess(..., AttributeTargetTypes = "MycompanyNamespace.Core.Services.*", ...)]

自定义约束

PostSharp 提供的 ComponentInternal 属性实现了 GrantAccess 方法,因此您可以将其应用于该用例。但是,似乎有一个错误不允许它在程序集级别多次应用。此问题应在即将发布的版本之一中修复。

若要使用类似的逻辑实现自定义约束,可以从以下示例开始:

[MulticastAttributeUsage(
    MulticastTargets.AnyType | MulticastTargets.Method | MulticastTargets.InstanceConstructor | MulticastTargets.Field,
    TargetTypeAttributes = MulticastAttributes.UserGenerated,
    TargetMemberAttributes = (MulticastAttributes.AnyVisibility & ~MulticastAttributes.Private) | MulticastAttributes.UserGenerated)]
[AttributeUsage(
    AttributeTargets.Assembly |
    AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Delegate,
    AllowMultiple = true)]
public class GrantAccessAttribute : ReferentialConstraint
{
    private string[] namespaces;
    public GrantAccessAttribute(params Type[] types)
    {
        this.namespaces = types.Select(t => t.Namespace).ToArray();
    }
    public override void ValidateCode(object target, Assembly assembly)
    {
        MemberInfo targetMember = target as MemberInfo;
        if (targetMember != null)
        {
            Type targetType = target as Type;
            if (targetType != null)
            {
                // validate derived types
                foreach (TypeInheritanceCodeReference reference in ReflectionSearch.GetDerivedTypes(targetType, ReflectionSearchOptions.IncludeTypeElement))
                {
                    Validate(reference);
                }
                // validate member references
                foreach (MemberTypeCodeReference reference in ReflectionSearch.GetMembersOfType(targetType, ReflectionSearchOptions.IncludeTypeElement))
                {
                    Validate(reference);
                }
            }
            // validate references in methods
            foreach (MethodUsageCodeReference methodUsageCodeReference in ReflectionSearch.GetMethodsUsingDeclaration(targetMember))
            {
                Validate(methodUsageCodeReference);
            }
        }
    }
    private void Validate(ICodeReference codeReference)
    {
        string usingNamespace = GetNamespace(codeReference.ReferencingDeclaration);
        string usedNamespace = GetNamespace(codeReference.ReferencedDeclaration);
        if (usingNamespace.Equals(usedNamespace, StringComparison.Ordinal))
            return;
        if (this.namespaces.Any(
            x => usingNamespace.Equals(x, StringComparison.Ordinal) ||
                 (usingNamespace.StartsWith(x, StringComparison.Ordinal) && usingNamespace[x.Length] == '.')))
            return;
        object[] arguments = new object[] {/*...*/};
        Message.Write(MessageLocation.Of(codeReference.ReferencingDeclaration), SeverityType.Warning, "ErrorCode", "Access error message.", arguments);
    }
    private string GetNamespace(object declarationObj)
    {
        Type declaringType = declarationObj as Type;
        if (declaringType == null)
        {
            MemberInfo declaringMember;
            ParameterInfo parameter;
            LocationInfo location;
            if ((declaringMember = declarationObj as MemberInfo) != null)
            {
                declaringType = declaringMember.DeclaringType;
            }
            else if ((location = declarationObj as LocationInfo) != null)
            {
                declaringType = location.DeclaringType;
            }
            else if ((parameter = declarationObj as ParameterInfo) != null)
            {
                declaringType = parameter.Member.DeclaringType;
            }
            else
            {
                throw new Exception("Invalid declaration object.");
            }
        }
        return declaringType.Namespace;
    }
}