您是否可以将扩展方法的范围限制为具有特定属性的类?

时间:2015-11-14 00:25:52

标签: c# attributes extension-methods postsharp

我们有一个自定义FileExtensionAttribute我们装饰我们的基于文件持久性的模型类。它的定义如下:

[AttributeUsage(AttributeTargets.Class, AllowMultiple=true, Inherited=true)]
public class FileExtensionAttribute : Attribute
{
    public FileExtensionAttribute(string fileExtension)
    {
        FileExtension = fileExtension;
    }

    public readonly string FileExtension;
}

我们还创建了以下扩展方法,以便更方便地检索这些扩展:

public static class FileExtensionAttributeHelper
{
    public static IEnumerable<string> GetFileExtensions(this Type type)
    {
        return type.CustomAttributes
            .OfType<FileExtensionAttribute>()
            .Select(fileExtensionAttribute => fileExtensionAttribute.FileExtension);
    }

    public static string GetPrimaryFileExtension(this Type type)
    {
        return GetFileExtensions(type).FirstOrDefault();
    }
}

在上面,对于没有指定属性的类型,这两个方法分别返回空枚举或null。但是,我们希望能够更积极主动地阻止此类呼叫。

虽然如果在指定类型上找不到这样的属性,我们可以轻松抛出异常,我想知道是否有办法将扩展方法的调用限制为仅支持首先设置了该属性的类型所以这是一个编译时错误,而不是必须在运行时处理的东西。

那么可以将扩展方法限制为仅支持具有给定属性的类型吗?如果是这样,怎么样?

注意:我认为在纯C#中可能无法做到这一点,但也许可以使用像PostSharp这样的东西。

2 个答案:

答案 0 :(得分:1)

目前不支持此功能。扩展方法是有限的,但可能非常强大。我最好奇为什么回到空列表是一个问题,我认为这将是理想的。如果它是空的或无效,那么什么也不做,不是什么大不了的事 - 生活还在继续。

更直接地回答你的问题,没有。您不能按编译时错误的属性限制扩展方法。

答案 1 :(得分:0)

PostSharp确实可以帮到你。

<强>概要

  • 创建AssemblyLevelAspect,使用ReflectionSearch搜索程序集中扩展方法的所有用途。这将给出一个调用这些扩展方法的方法列表。
  • 对于所有这些方法,请使用ISyntaxReflectionService获取语法树。它是IL语法树而不是源代码本身。
  • 搜索typeof(X).GetFileExtensions()variable.GetType.GetFileExtensions()等模式,并验证传递的类型是否具有FileExtension属性。
  • 如果发现使用不正确,请编写编译时错误。

<强>来源:

[MulticastAttributeUsage(PersistMetaData = true)]
public class FileExtensionValidationPolicy : AssemblyLevelAspect
{
    public override bool CompileTimeValidate( Assembly assembly )
    {
        ISyntaxReflectionService reflectionService = PostSharpEnvironment.CurrentProject.GetService<ISyntaxReflectionService>();

        MethodInfo[] validatedMethods = new[]
        {
            typeof(FileExtensionAttributeHelper).GetMethod( "GetFileExtensions", BindingFlags.Public | BindingFlags.Static ),
            typeof(FileExtensionAttributeHelper).GetMethod( "GetPrimaryFileExtension", BindingFlags.Public | BindingFlags.Static )
        };

        MethodBase[] referencingMethods =
            validatedMethods
                .SelectMany( ReflectionSearch.GetMethodsUsingDeclaration )
                .Select( r => r.UsingMethod )
                .Where( m => !validatedMethods.Contains( m ) )
                .Distinct()
                .ToArray();

        foreach ( MethodBase userMethod in referencingMethods )
        {
            ISyntaxMethodBody body = reflectionService.GetMethodBody( userMethod, SyntaxAbstractionLevel.ExpressionTree );

            ValidateMethodBody(body, userMethod, validatedMethods);
        }

        return false;
    }

    private void ValidateMethodBody(ISyntaxMethodBody methodBody, MethodBase userMethod, MethodInfo[] validatedMethods)
    {
        MethodBodyValidator validator = new MethodBodyValidator(userMethod, validatedMethods);

        validator.VisitMethodBody(methodBody);
    }

    private class MethodBodyValidator : SyntaxTreeVisitor
    {
        private MethodBase userMethod;
        private MethodInfo[] validatedMethods;

        public MethodBodyValidator( MethodBase userMethod, MethodInfo[] validatedMethods )
        {
            this.userMethod = userMethod;
            this.validatedMethods = validatedMethods;
        }

        public override object VisitMethodCallExpression( IMethodCallExpression expression )
        {
            foreach ( MethodInfo validatedMethod in this.validatedMethods )
            {
                if ( validatedMethod != expression.Method )
                    continue;

                this.ValidateTypeOfExpression(validatedMethod, expression.Arguments[0]);
                this.ValidateGetTypeExpression(validatedMethod, expression.Arguments[0]);
            }

            return base.VisitMethodCallExpression( expression );
        }

        private void ValidateTypeOfExpression(MethodInfo validatedMethod, IExpression expression)
        {
            IMethodCallExpression callExpression = expression as IMethodCallExpression;

            if (callExpression == null)
                return;

            if (callExpression.Method != typeof(Type).GetMethod("GetTypeFromHandle"))
                return;

            IMetadataExpression metadataExpression = callExpression.Arguments[0] as IMetadataExpression;

            if (metadataExpression == null)
                return;

            Type type = metadataExpression.Declaration as Type;

            if (type == null)
                return;

            if (!type.GetCustomAttributes(typeof(FileExtensionAttribute)).Any())
            {
                MessageSource.MessageSink.Write(
                    new Message(
                        MessageLocation.Of( this.userMethod ),
                        SeverityType.Error, "MYERR1",
                        String.Format( "Calling method {0} on type {1} is not allowed.", validatedMethod, type ),
                        null, null, null
                        )
                    );
            }
        }

        private void ValidateGetTypeExpression(MethodInfo validatedMethod, IExpression expression)
        {
            IMethodCallExpression callExpression = expression as IMethodCallExpression;

            if (callExpression == null)
                return;

            if (callExpression.Method != typeof(object).GetMethod("GetType"))
                return;

            IExpression instanceExpression = callExpression.Instance;

            Type type = instanceExpression.ReturnType;

            if (type == null)
                return;

            if (!type.GetCustomAttributes(typeof(FileExtensionAttribute)).Any())
            {
                MessageSource.MessageSink.Write(
                    new Message(
                        MessageLocation.Of(this.userMethod),
                        SeverityType.Error, "MYERR1",
                        String.Format("Calling method {0} on type {1} is not allowed.", validatedMethod, type),
                        null, null, null
                        )
                    );
            }
        }
    }
}

<强>用法:

[assembly: FileExtensionValidationPolicy(
               AttributeInheritance = MulticastInheritance.Multicast
               )]

备注:

  • [MulticastAttributeUsage(PersistMetaData = true)]AttributeInheritance = MulticastInheritance.Multicast都需要保留程序集上的属性,以便在引用声明项目的项目上执行分析。
  • 可能需要更深入的分析来正确处理派生类和其他特殊情况。
  • 需要PostSharp专业版许可证。