Say we have a typical piece of forms code around a GridControl.
private GridControl myGrid;
internal void InitialiseGrid()
{
myGrid.BeginUpdate();
try
{
....
}
finally
{
myGrid.EndUpdate();
}
}
Now lets say that I wanted to use PostSharp or something to wrap this type of behaviour in a crosscut so that the final code would look similar to:
private GridControl myGrid;
[MyGridControlUpdateAspect(FieldName="myGrid")]
internal void InitialiseGrid()
{
....
}
Given the constant objection in SO and other places to not using reflection to access private fields in classes, can anybody provide a better means of accessing myGrid and invoking the BeginUpdate and EndUpdate methods within the aspect source code, in such a manner that the reference to the particular grid can be passed in some way to the aspect, and still satisfy the purists.
UPDATE: The following is a real world example of having code that would otherwise be wrapped in try/finally block for changing cursor on entry to a method. By utilising an aspect to perform this functionality I can add this function to many methods that may take time, without specifically having to add this functionality into any specific code piece.
[ChangeCursor(CursorPropertyName = "Cursor", NewCursorTypeName = "WaitCursor", AspectPriority = 8)]
internal void SomeButtonClick(object sender, System.EventArgs args)...
or
[assembly: ChangeCursor(CursorPropertyName = "Cursor", NewCursorTypeName = "WaitCursor", AttributeTargetTypes = "SomeNamespace.*", AttributeTargetMembers = "regex:.*ButtonClick", AttributePriority = 30, AspectPriority = 12)]
Aspect code (note the use of Reflection - in this case it uses the actual instance rather than a field within the instance, but the concept is the same).
/// <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;
}
}
While accessing private field through reflection is generally not a good practice (and may not work in restricted security settings), you have to remember that the (PostSharp) aspect code that uses reflection usually runs in compile-time only. PostSharp uses reflection API's for convenience, because users are familiar with it.
In the first example, the problem is referencing a field by name, which is possibly not transparent to refactoring tools and generally not clean. In this case it is a bit harder to work around that - I will just sketch the solution in the end.
In the second example, you are using reflection in RuntimeInitialize, which is what so-called purists would be criticizing. It is possible to reduce the reflection and aspect argument count. PostSharp allows you to introduce aspects dynamically using IAspectProvider interface and IAdviceProvider interfaces.
See the following for demonstration of removing unnecessary reflection from runtime:
[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;
}
}
There are two aspects - first one introduces an interface to target class that will be used to obtain Cursor property value. The second aspect applies to method. In compile time it makes sure that the first one is present on the type and checks that the target cursor exists. In runtime it sets the cursor through the interface without any reflection. Only runtime reflection is obtaining the cursor from the public static property (for sake of brevity).
To give you some food for thought, you may use PostSharp to do more advanced transformations to achieve what you are doing in a more succinct way that removes the problem with referencing by name. See ISyntaxReflectionService interface, which allows you to get abstract syntax tree of a method (CIL, not C#). You can use this interface to analyze the method and decide on which fields you need to call BeginUpdate and EndUpdate.