围绕方面和现场注入的设计惯例

时间:2015-01-21 23:40:44

标签: c# winforms postsharp

假设我们在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)]

Aspect代码(注意使用Reflection - 在这种情况下,它使用实际实例而不是实例中的字段,但概念是相同的。)

/// <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;
    }
}

1 个答案:

答案 0 :(得分:1)

虽然通过反射访问私有字段通常不是一个好习惯(并且可能无法在受限制的安全设置中工作),但您必须记住使用反射的(PostSharp)方面代码通常仅在编译时运行。 PostSharp使用反射API来方便,因为用户熟悉它。

在第一个示例中,问题是按名称引用字段,这可能对重构工具不透明,而且通常不干净。在这种情况下,解决这个问题要困难一些 - 我最后将简要介绍解决方案。

在第二个例子中,你在 RuntimeInitialize 中使用反射,这就是所谓的纯粹主义者会批评的。可以减少反射和方面参数计数。 PostSharp允许您使用 IAspectProvider 界面和 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;
    }
}

有两个方面 - 第一个是介绍目标类的接口,用于获取 Cursor 属性值。第二方面适用于方法。在编译时,它确保第一个存在于类型上并检查目标游标是否存在。在运行时,它通过界面设置光标而不进行任何反射。只有运行时反射才能从公共静态属性中获取游标(为了简洁起见)。

为了给您一些思考,您可以使用PostSharp进行更高级的转换,以更简洁的方式实现您的工作,从而通过名称引用来消除问题。请参阅ISyntaxReflectionService接口,它允许您获取方法的抽象语法树(CIL,而不是C#)。您可以使用此界面分析方法并确定需要调用 BeginUpdate EndUpdate 的字段。