围绕方面和字段注入的设计约定
Design convention around aspects and field injection
假设我们有一段围绕 GridControl 的典型表单代码。
private GridControl myGrid;
internal void InitialiseGrid()
{
myGrid.BeginUpdate();
try
{
....
}
finally
{
myGrid.EndUpdate();
}
}
现在假设我想使用 PostSharp 或其他工具将此类行为包装在横切中,以便最终代码看起来类似于:
private GridControl myGrid;
[MyGridControlUpdateAspect(FieldName="myGrid")]
internal void InitialiseGrid()
{
....
}
鉴于 SO 和其他地方不断反对不使用反射来访问 类 中的私有字段,任何人都可以提供更好的方法来访问 myGrid 并在方面源代码中调用 BeginUpdate 和 EndUpdate 方法,以这种方式可以将对特定网格的引用以某种方式传递给方面,并且仍然满足纯粹主义者的要求。
更新:以下是一个真实世界的示例,该示例包含在 try/finally 块中用于在进入方法时更改光标的代码。通过利用一个方面来执行此功能,我可以将此功能添加到许多可能需要时间的方法中,而无需专门将此功能添加到任何特定代码段中。
[ChangeCursor(CursorPropertyName = "Cursor", NewCursorTypeName = "WaitCursor", AspectPriority = 8)]
internal void SomeButtonClick(object sender, System.EventArgs args)...
或
[assembly: ChangeCursor(CursorPropertyName = "Cursor", NewCursorTypeName = "WaitCursor", AttributeTargetTypes = "SomeNamespace.*", AttributeTargetMembers = "regex:.*ButtonClick", AttributePriority = 30, AspectPriority = 12)]
方面代码(注意反射的使用——在本例中它使用实际实例而不是实例中的字段,但概念是相同的)。
/// <summary>
/// Aspect to set the cursor for a windows form to a particular
/// cursor type and reset it back to the default type on exit
/// </summary>
[Serializable]
[AttributeUsage(AttributeTargets.Method)]
[MulticastAttributeUsage(MulticastTargets.Method)]
public sealed class ChangeCursorAttribute : OnMethodBoundaryAspect
{
/// <summary>
/// The name of the property that will be available in the instance
/// of the method that this aspect advises.
/// <para>It is expected to derive from System.Windows.Forms but
/// does not necessarily have to provided it has a System.Windows.Form.Cursor property
/// that matches this name</para>
/// </summary>
public string CursorPropertyName { get; set; }
/// <summary>
/// The name of the cursor to set to a standard System.Windows.Forms.Cursors type
/// </summary>
public string NewCursorTypeName { get; set; }
/// <summary>
/// The type of the cursor to set on entry
/// </summary>
private Cursor NewCursorType { get; set; }
/// <summary>
/// The property info for the cursor property name
/// </summary>
private PropertyInfo CursorPropertyInfo { get; set; }
/// <summary>
/// The aspect is advising on an extension method
/// instead of a method in the class with the Cursors attribute
/// </summary>
private bool IsExtensionMethodAttribute { get; set; }
/// <summary>
/// Validate the necessary properties are set in the attribute at compile time
/// </summary>
/// <param name="method"></param>
/// <returns></returns>
public override bool CompileTimeValidate(MethodBase method)
{
if (CursorPropertyName == null)
throw new InvalidAnnotationException(string.Format("CursorPropertyName must be defined: {0}.{1}", method.DeclaringType.FullName, method.Name));
if (NewCursorTypeName == null)
throw new InvalidAnnotationException(string.Format("NewCursorType must be defined: {0}.{1}", method.DeclaringType.FullName, method.Name));
return base.CompileTimeValidate(method);
}
/// <summary>
/// Initialise the information required for this attribute
/// at runtime
/// </summary>
/// <param name="method"></param>
public override void RuntimeInitialize(MethodBase method)
{
base.RuntimeInitialize(method);
PropertyInfo pi = typeof(Cursors).GetProperty(NewCursorTypeName, BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
NewCursorType = (Cursor)pi.GetValue(null, null);
try
{
// If attribute associated with extension method use the type of the
// first parameter to associate the property with
if (method.IsDefined(typeof(ExtensionAttribute), false))
{
ParameterInfo paramInfo = method.GetParameters()[0];
Type type1 = paramInfo.ParameterType;
CursorPropertyInfo = type1.GetProperty(CursorPropertyName);
IsExtensionMethodAttribute = true;
}
else
CursorPropertyInfo = method.DeclaringType.GetProperty(CursorPropertyName);
}
catch (Exception ex)
{
throw new InvalidAnnotationException(string.Format("CursorPropertyName {2} not found in type: {0}.{1}\n{3}\n", method.DeclaringType.FullName, method.Name, CursorPropertyName, ex.GetType().FullName, ex.Message));
}
}
/// <summary>
/// On entry to a method set the cursor type to the required
/// type as specified in the attribute arguments
/// </summary>
/// <param name="args">The arguments to the method</param>
public override sealed void OnEntry(MethodExecutionArgs args)
{
CursorPropertyInfo.SetValue(GetInstance(args), NewCursorType, null);
}
/// <summary>
/// On method exit, regardless of success or failure reset
/// the form cursor to the default cursor type
/// </summary>
/// <param name="args">The arguments to the method</param>
public override sealed void OnExit(MethodExecutionArgs args)
{
CursorPropertyInfo.SetValue(GetInstance(args), Cursors.Default, null);
}
/// <summary>
/// Get the object instance that contains the Cursor property
/// depending on whether this attribute is attached to a method
/// within a class or an extension method
/// </summary>
/// <param name="args">The arguments to the method</param>
/// <returns>The instance object</returns>
private object GetInstance(MethodExecutionArgs args)
{
object instance = args.Instance;
if (IsExtensionMethodAttribute)
instance = args.Arguments[0];
return instance;
}
}
虽然通过反射访问私有字段通常不是一个好的做法(并且可能无法在受限的安全设置中工作),但您必须记住,使用反射的 (PostSharp) 方面代码通常仅在编译时运行。 PostSharp 使用反射 API 是为了方便,因为用户对此很熟悉。
在第一个示例中,问题是按名称引用字段,这可能对重构工具不透明并且通常不干净。在这种情况下,要解决这个问题会有点困难 - 最后我只会勾勒出解决方案。
在第二个示例中,您在 RuntimeInitialize 中使用了反射,这是所谓的纯粹主义者会批评的。可以减少反射和方面参数计数。 PostSharp 允许您使用 IAspectProvider interface and IAdviceProvider 接口动态引入方面。
有关从运行时删除不必要的反射的演示,请参见以下内容:
[Serializable]
[IntroduceInterface(typeof(ICursorProperty))]
public class CursorPropertyTypeAttribute : TypeLevelAspect, ICursorProperty, IAdviceProvider, IInstanceScopedAspect
{
public Property<Cursor> Cursor;
Cursor ICursorProperty.Cursor { get { return Cursor.Get(); } set { Cursor.Set(value); } }
public IEnumerable<AdviceInstance> ProvideAdvices( object targetElement )
{
yield return new ImportLocationAdviceInstance(this.GetType().GetField("Cursor", BindingFlags.Public | BindingFlags.Instance), this.FindCursorProperty((Type)targetElement));
}
public LocationInfo FindCursorProperty(Type targetType)
{
foreach ( PropertyInfo property in targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance) )
{
if ( null != property.GetCustomAttribute( typeof(CursorPropertyAttribute) ) )
return new LocationInfo( property );
}
return null;
}
public object CreateInstance(AdviceArgs adviceArgs)
{
return this.MemberwiseClone();
}
public void RuntimeInitializeInstance()
{
}
}
public interface ICursorProperty
{
Cursor Cursor { get; set; }
}
[Serializable]
[AspectTypeDependency(AspectDependencyAction.Order, AspectDependencyPosition.After, typeof(CursorPropertyTypeAttribute))]
public class ChangeCursorAttribute : OnMethodBoundaryAspect, IAspectProvider
{
private string cursorName;
[NonSerialized]
private Cursor cursor;
public ChangeCursorAttribute( string cursorName )
{
this.cursorName = cursorName;
}
public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
{
Type type = ((MethodBase) targetElement).DeclaringType;
IAspectRepositoryService repository = PostSharpEnvironment.CurrentProject.GetService<IAspectRepositoryService>();
if ( !repository.HasAspect( type, typeof(CursorPropertyTypeAttribute) ) )
yield return new AspectInstance( type, new CursorPropertyTypeAttribute() );
}
public override void CompileTimeInitialize( MethodBase method, AspectInfo aspectInfo )
{
if ( null == typeof(Cursors).GetProperty( this.cursorName, BindingFlags.Public | BindingFlags.Static ) )
MessageSource.MessageSink.Write( new Message( MessageLocation.Of( method ), SeverityType.Error, "USR001", "Invalid cursor name", null, "MyComponent",
null ) );
}
public override void RuntimeInitialize( MethodBase method )
{
this.cursor = (Cursor) typeof(Cursors).GetProperty( this.cursorName, BindingFlags.Public | BindingFlags.Static ).GetValue( null );
}
public override void OnEntry(MethodExecutionArgs args)
{
(args.Instance as ICursorProperty).Cursor = cursor;
}
public override void OnExit(MethodExecutionArgs args)
{
(args.Instance as ICursorProperty).Cursor = Cursors.DefaultCursor;
}
}
有两个方面 - 第一个是引入目标 class 的接口,该接口将用于获取 Cursor 属性 值。第二个方面适用于方法。在编译时,它确保第一个出现在类型上并检查目标游标是否存在。在运行时,它通过界面设置光标而不进行任何反射。只有运行时反射从 public static 属性 获取光标(为简洁起见)。
为了给您一些启发,您可以使用 PostSharp 进行更高级的转换,以更简洁的方式实现您正在做的事情,从而消除按名称引用的问题。请参阅 ISyntaxReflectionService 接口,它允许您获取方法的抽象语法树(CIL,而不是 C#)。您可以使用此接口来分析方法并决定在哪些字段上需要调用 BeginUpdate 和 EndUpdate.
假设我们有一段围绕 GridControl 的典型表单代码。
private GridControl myGrid;
internal void InitialiseGrid()
{
myGrid.BeginUpdate();
try
{
....
}
finally
{
myGrid.EndUpdate();
}
}
现在假设我想使用 PostSharp 或其他工具将此类行为包装在横切中,以便最终代码看起来类似于:
private GridControl myGrid;
[MyGridControlUpdateAspect(FieldName="myGrid")]
internal void InitialiseGrid()
{
....
}
鉴于 SO 和其他地方不断反对不使用反射来访问 类 中的私有字段,任何人都可以提供更好的方法来访问 myGrid 并在方面源代码中调用 BeginUpdate 和 EndUpdate 方法,以这种方式可以将对特定网格的引用以某种方式传递给方面,并且仍然满足纯粹主义者的要求。
更新:以下是一个真实世界的示例,该示例包含在 try/finally 块中用于在进入方法时更改光标的代码。通过利用一个方面来执行此功能,我可以将此功能添加到许多可能需要时间的方法中,而无需专门将此功能添加到任何特定代码段中。
[ChangeCursor(CursorPropertyName = "Cursor", NewCursorTypeName = "WaitCursor", AspectPriority = 8)]
internal void SomeButtonClick(object sender, System.EventArgs args)...
或
[assembly: ChangeCursor(CursorPropertyName = "Cursor", NewCursorTypeName = "WaitCursor", AttributeTargetTypes = "SomeNamespace.*", AttributeTargetMembers = "regex:.*ButtonClick", AttributePriority = 30, AspectPriority = 12)]
方面代码(注意反射的使用——在本例中它使用实际实例而不是实例中的字段,但概念是相同的)。
/// <summary>
/// Aspect to set the cursor for a windows form to a particular
/// cursor type and reset it back to the default type on exit
/// </summary>
[Serializable]
[AttributeUsage(AttributeTargets.Method)]
[MulticastAttributeUsage(MulticastTargets.Method)]
public sealed class ChangeCursorAttribute : OnMethodBoundaryAspect
{
/// <summary>
/// The name of the property that will be available in the instance
/// of the method that this aspect advises.
/// <para>It is expected to derive from System.Windows.Forms but
/// does not necessarily have to provided it has a System.Windows.Form.Cursor property
/// that matches this name</para>
/// </summary>
public string CursorPropertyName { get; set; }
/// <summary>
/// The name of the cursor to set to a standard System.Windows.Forms.Cursors type
/// </summary>
public string NewCursorTypeName { get; set; }
/// <summary>
/// The type of the cursor to set on entry
/// </summary>
private Cursor NewCursorType { get; set; }
/// <summary>
/// The property info for the cursor property name
/// </summary>
private PropertyInfo CursorPropertyInfo { get; set; }
/// <summary>
/// The aspect is advising on an extension method
/// instead of a method in the class with the Cursors attribute
/// </summary>
private bool IsExtensionMethodAttribute { get; set; }
/// <summary>
/// Validate the necessary properties are set in the attribute at compile time
/// </summary>
/// <param name="method"></param>
/// <returns></returns>
public override bool CompileTimeValidate(MethodBase method)
{
if (CursorPropertyName == null)
throw new InvalidAnnotationException(string.Format("CursorPropertyName must be defined: {0}.{1}", method.DeclaringType.FullName, method.Name));
if (NewCursorTypeName == null)
throw new InvalidAnnotationException(string.Format("NewCursorType must be defined: {0}.{1}", method.DeclaringType.FullName, method.Name));
return base.CompileTimeValidate(method);
}
/// <summary>
/// Initialise the information required for this attribute
/// at runtime
/// </summary>
/// <param name="method"></param>
public override void RuntimeInitialize(MethodBase method)
{
base.RuntimeInitialize(method);
PropertyInfo pi = typeof(Cursors).GetProperty(NewCursorTypeName, BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
NewCursorType = (Cursor)pi.GetValue(null, null);
try
{
// If attribute associated with extension method use the type of the
// first parameter to associate the property with
if (method.IsDefined(typeof(ExtensionAttribute), false))
{
ParameterInfo paramInfo = method.GetParameters()[0];
Type type1 = paramInfo.ParameterType;
CursorPropertyInfo = type1.GetProperty(CursorPropertyName);
IsExtensionMethodAttribute = true;
}
else
CursorPropertyInfo = method.DeclaringType.GetProperty(CursorPropertyName);
}
catch (Exception ex)
{
throw new InvalidAnnotationException(string.Format("CursorPropertyName {2} not found in type: {0}.{1}\n{3}\n", method.DeclaringType.FullName, method.Name, CursorPropertyName, ex.GetType().FullName, ex.Message));
}
}
/// <summary>
/// On entry to a method set the cursor type to the required
/// type as specified in the attribute arguments
/// </summary>
/// <param name="args">The arguments to the method</param>
public override sealed void OnEntry(MethodExecutionArgs args)
{
CursorPropertyInfo.SetValue(GetInstance(args), NewCursorType, null);
}
/// <summary>
/// On method exit, regardless of success or failure reset
/// the form cursor to the default cursor type
/// </summary>
/// <param name="args">The arguments to the method</param>
public override sealed void OnExit(MethodExecutionArgs args)
{
CursorPropertyInfo.SetValue(GetInstance(args), Cursors.Default, null);
}
/// <summary>
/// Get the object instance that contains the Cursor property
/// depending on whether this attribute is attached to a method
/// within a class or an extension method
/// </summary>
/// <param name="args">The arguments to the method</param>
/// <returns>The instance object</returns>
private object GetInstance(MethodExecutionArgs args)
{
object instance = args.Instance;
if (IsExtensionMethodAttribute)
instance = args.Arguments[0];
return instance;
}
}
虽然通过反射访问私有字段通常不是一个好的做法(并且可能无法在受限的安全设置中工作),但您必须记住,使用反射的 (PostSharp) 方面代码通常仅在编译时运行。 PostSharp 使用反射 API 是为了方便,因为用户对此很熟悉。
在第一个示例中,问题是按名称引用字段,这可能对重构工具不透明并且通常不干净。在这种情况下,要解决这个问题会有点困难 - 最后我只会勾勒出解决方案。
在第二个示例中,您在 RuntimeInitialize 中使用了反射,这是所谓的纯粹主义者会批评的。可以减少反射和方面参数计数。 PostSharp 允许您使用 IAspectProvider interface and IAdviceProvider 接口动态引入方面。
有关从运行时删除不必要的反射的演示,请参见以下内容:
[Serializable]
[IntroduceInterface(typeof(ICursorProperty))]
public class CursorPropertyTypeAttribute : TypeLevelAspect, ICursorProperty, IAdviceProvider, IInstanceScopedAspect
{
public Property<Cursor> Cursor;
Cursor ICursorProperty.Cursor { get { return Cursor.Get(); } set { Cursor.Set(value); } }
public IEnumerable<AdviceInstance> ProvideAdvices( object targetElement )
{
yield return new ImportLocationAdviceInstance(this.GetType().GetField("Cursor", BindingFlags.Public | BindingFlags.Instance), this.FindCursorProperty((Type)targetElement));
}
public LocationInfo FindCursorProperty(Type targetType)
{
foreach ( PropertyInfo property in targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance) )
{
if ( null != property.GetCustomAttribute( typeof(CursorPropertyAttribute) ) )
return new LocationInfo( property );
}
return null;
}
public object CreateInstance(AdviceArgs adviceArgs)
{
return this.MemberwiseClone();
}
public void RuntimeInitializeInstance()
{
}
}
public interface ICursorProperty
{
Cursor Cursor { get; set; }
}
[Serializable]
[AspectTypeDependency(AspectDependencyAction.Order, AspectDependencyPosition.After, typeof(CursorPropertyTypeAttribute))]
public class ChangeCursorAttribute : OnMethodBoundaryAspect, IAspectProvider
{
private string cursorName;
[NonSerialized]
private Cursor cursor;
public ChangeCursorAttribute( string cursorName )
{
this.cursorName = cursorName;
}
public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
{
Type type = ((MethodBase) targetElement).DeclaringType;
IAspectRepositoryService repository = PostSharpEnvironment.CurrentProject.GetService<IAspectRepositoryService>();
if ( !repository.HasAspect( type, typeof(CursorPropertyTypeAttribute) ) )
yield return new AspectInstance( type, new CursorPropertyTypeAttribute() );
}
public override void CompileTimeInitialize( MethodBase method, AspectInfo aspectInfo )
{
if ( null == typeof(Cursors).GetProperty( this.cursorName, BindingFlags.Public | BindingFlags.Static ) )
MessageSource.MessageSink.Write( new Message( MessageLocation.Of( method ), SeverityType.Error, "USR001", "Invalid cursor name", null, "MyComponent",
null ) );
}
public override void RuntimeInitialize( MethodBase method )
{
this.cursor = (Cursor) typeof(Cursors).GetProperty( this.cursorName, BindingFlags.Public | BindingFlags.Static ).GetValue( null );
}
public override void OnEntry(MethodExecutionArgs args)
{
(args.Instance as ICursorProperty).Cursor = cursor;
}
public override void OnExit(MethodExecutionArgs args)
{
(args.Instance as ICursorProperty).Cursor = Cursors.DefaultCursor;
}
}
有两个方面 - 第一个是引入目标 class 的接口,该接口将用于获取 Cursor 属性 值。第二个方面适用于方法。在编译时,它确保第一个出现在类型上并检查目标游标是否存在。在运行时,它通过界面设置光标而不进行任何反射。只有运行时反射从 public static 属性 获取光标(为简洁起见)。
为了给您一些启发,您可以使用 PostSharp 进行更高级的转换,以更简洁的方式实现您正在做的事情,从而消除按名称引用的问题。请参阅 ISyntaxReflectionService 接口,它允许您获取方法的抽象语法树(CIL,而不是 C#)。您可以使用此接口来分析方法并决定在哪些字段上需要调用 BeginUpdate 和 EndUpdate.