2015-01-21 2 views
1

GridControl 주변에 전형적인 코드 조각이 있다고 가정 해보십시오.aspect와 필드 인젝션을 둘러싼 디자인 컨셉

private GridControl myGrid; 

internal void InitialiseGrid() 
{ 
    myGrid.BeginUpdate(); 
    try 
    { 
     .... 
    } 
    finally 
    { 
     myGrid.EndUpdate(); 
    } 
} 

지금은 최종 코드는 비슷하게 있도록 샛길에서 행동의 유형을 래핑하는 PostSharp 또는 무언가를 사용하고 싶다고 말할 수 : SO의 지속적인 반대 감안할 때

private GridControl myGrid; 

[MyGridControlUpdateAspect(FieldName="myGrid")] 
internal void InitialiseGrid() 
{ 
    .... 
} 

및 리플렉션을 사용하여 클래스의 비공개 필드에 액세스하지 못하게하는 경우 다른 사용자가 myGrid에 액세스하고 aspect 소스 코드 내에서 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; 
    } 
} 

답변

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

는 두 가지 측면이 있습니다

런타임에서 불필요한 반사를 제거하는 시연을 위해 다음을 참조하십시오. 두 번째 측면은 메소드에 적용됩니다. 컴파일 시간에 첫 번째 유형이 유형에 있는지 확인하고 대상 커서가 있는지 확인합니다. 런타임시 아무런 반향없이 인터페이스를 통해 커서를 설정합니다. 런타임 리플렉션 만 public static 속성에서 커서를 가져옵니다 (간결성을 위해).

생각하기에 음식을 줄 수 있도록 PostSharp를 사용하여 더 복잡한 변환을 수행하여 이름으로 참조 할 때 문제를 제거하는보다 간결한 방법으로 작업을 수행 할 수 있습니다. ISyntaxReflectionService 인터페이스를 보면 메서드의 추상 구문 트리 (C#이 아닌 C#)를 얻을 수 있습니다. 이 인터페이스를 사용하여 메소드를 분석하고 호출해야하는 필드를 결정할 수 있습니다. BeginUpdate최종 업데이트.