您能否将扩展方法的范围限制为具有特定属性的 类?
Can you restrict the scope of an Extension Method to classes with a specific attribute?
我们有一个自定义 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();
}
}
在上面,对于没有指定属性的类型,两种方法return分别是空枚举或null。但是,我们希望更主动地首先阻止此类调用。
如果在指定类型上找不到此类属性,我们可以很容易地抛出异常,但我想知道是否有办法将扩展方法的调用限制为仅支持在第一名,所以这是一个编译时错误,而不是必须在 运行 时处理的问题。
那么是否可以将扩展方法限制为仅支持具有给定属性的类型?如果可以,怎么做?
注意:我认为这在纯 C# 中可能是不可能的,但也许可以使用类似 PostSharp 的东西。
目前不支持。扩展方法是有限的,但可以非常强大。我很好奇为什么返回一个空列表是一个问题,我认为这将是理想的。如果它为空或 null 则什么也不做,没什么大不了的——生活还要继续。
为了更直接地回答你的问题,不。对于编译时错误,您不能通过属性限制扩展方法。
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 Professional 许可证。
我们有一个自定义 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();
}
}
在上面,对于没有指定属性的类型,两种方法return分别是空枚举或null。但是,我们希望更主动地首先阻止此类调用。
如果在指定类型上找不到此类属性,我们可以很容易地抛出异常,但我想知道是否有办法将扩展方法的调用限制为仅支持在第一名,所以这是一个编译时错误,而不是必须在 运行 时处理的问题。
那么是否可以将扩展方法限制为仅支持具有给定属性的类型?如果可以,怎么做?
注意:我认为这在纯 C# 中可能是不可能的,但也许可以使用类似 PostSharp 的东西。
目前不支持。扩展方法是有限的,但可以非常强大。我很好奇为什么返回一个空列表是一个问题,我认为这将是理想的。如果它为空或 null 则什么也不做,没什么大不了的——生活还要继续。
为了更直接地回答你的问题,不。对于编译时错误,您不能通过属性限制扩展方法。
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 Professional 许可证。