Monday, 20 February 2017

Override Static Method

Hi All,

Unfortunately sometimes during customization you may need to override some logic in static methods and that is really bad task, as it requires to copy a lot of code.
Here I will show you few examples that can give you idea how you can fix it.

In general there is no way to really override static method, the only one way how you can replace it's logic is just define a new one and replace all places where it is called.

3 different scenarios will be based on Acumatica Fixed Assets module:
  • Not so good - static method on event
    • AssetMaint.LiveUpdateMaskedSubs(PXGraph graph, PXCache facache, FALocationHistory lochist);
  • Bad - static method on dynamically subscribed event
    • AssetMaint.MakeSubID<MaskField, SubIDField>(PXCache sender, FixedAsset asset);
  • Very Bad - static method on Persist
    • AssetProces.TransferAsset(PXGraph graph, FixedAsset asset, FALocationHistory location, ref FARegister register);
Lets start:
DISCLAIMER: All examples here are not a best practice and might not be supported my Acumatica in the future. So if you are going to use it, please do it on your own risk.
Great power comes with great responsibility.

Static Method on Event
In AssetsMaint we have method AssetMaint.LiveUpdateMaskedSubs(...) that recalculates sub-accounts.

That method is called from FALocationHistory_RowInserted:
public virtual void FALocationHistory_RowInserted(PXCache sender, PXRowInsertedEventArgs e)
{
       LiveUpdateMaskedSubs(this, Asset.Cache, (FALocationHistory)e.Row);
}

Hopefully that one we can easily override using extensions:
public class AssetMaintExt : PXGraphExtension<AssetMaint>
{
       public virtual void FALocationHistory_RowInserted(PXCache sender,
PXRowInsertedEventArgs e, PXRowInserted del)
       {
              AssetMaintExt.LiveUpdateMaskedSubs(Base,
Base.Asset.Cache, (FALocationHistory)e.Row);
       }
       public static void LiveUpdateMaskedSubs(PXGraph graph, PXCache facache,
FALocationHistory lochist)
       {
              ......
       }
}
Here I'm overriding RowInserted event with 3rd parameter, call my own static method and ignore base event. In this case original static method will not be called at all.


Static Method on Dynamically Subscribed Event
Inside AssetMaint constructor we have few dynamic subscriptions like this:

public AssetMaint()
{     
FieldDefaulting.AddHandler<FixedAsset.fASubID>(
FASubIDFieldDefaulting<FixedAsset.fASubMask, FixedAsset.fASubID>);
}
protected static void FASubIDFieldDefaulting<MaskField, SubIDField>(PXCache sender,
PXFieldDefaultingEventArgs e)
       where MaskField : IBqlField
       where SubIDField : IBqlField
{
       …
}
FASubIDFieldDefaulting is static method that is used as event handler. 

Unfortunately that type of events we cannot override with Extensions, so we need to unsubscribe base event and subscribe ours:
public class AssetMaintExt : PXGraphExtension<AssetMaint>
{
       public override void Initialize()
       {
              base.Initialize();

              MethodInfo mi = Base.GetType().GetMethod("FASubIDFieldDefaulting",
BindingFlags.Static | BindingFlags.NonPublic);
              mi = mi.MakeGenericMethod(
new[] { typeof(FixedAsset.fASubMask), typeof(FixedAsset.fASubID) });
              Delegate de = mi.CreateDelegate(typeof(PXFieldDefaulting));

              Base.FieldDefaulting.RemoveHandler<FixedAsset.fASubID>((PXFieldDefaulting)de);
       }

       public void FixedAsset_FASubID_FieldDefaulting(PXCache sender,
              PXFieldDefaultingEventArgs e, PXFieldDefaulting del)
       {
              …
}
}
Here using Initialize method we can unsubscribe base event. Unfortunately my static method is protected, so I have to use reflection to get signature and create generic arguments.
New event i'm subscribing with standard events declaration withing extension.


Static Method on Persist
And last example is worst: There is call of static method TransferAsset right in Persist() inside AssetMaint:
public class AssetMaint : PXGraph<AssetMaint, FixedAsset>
{
public override void Persist()
       {
              …
              AssetProcess.TransferAsset(this, asset, currentLocation, ref transferreg);
              …             
       }
}
The trouble here, that is that AssetMaint also overrides Persist of PXGraph and we have to call PXGraph.Persist() instance method to save data. In the same time we need to skip call on AssetMaint.Persist() method with all body.
That is a trouble as it goes again polymorphism principle of OOP - inf you have instance you can call only overriding method, but you cannot call overridden.
Ok, and what to do here, if we still need to save data to database, but should not call base static method? Thanks to Kenneth Xu example, we actually can call grandparent method using dynamic code snippet on IL:
public class AssetMaintExt : PXGraphExtension<AssetMaint>
{
       public delegate void PersistDelegate();
       [PXOverride]
       public void Persist(PersistDelegate baseMethod)
       {
              …
              AssetProcessExt.TransferAsset(Base, asset, currentLocation, ref transferreg);
              …

              //base.Persist();
              MethodInfo fooA = typeof(PXGraph).GetMethod("Persist",
BindingFlags.Public | BindingFlags.Instance, null, new Type[] {}, null);
              DynamicMethod baseBasePersist = new DynamicMethod("foo_A",
                      nullnew[] { typeof(PXGraph) }, typeof(PXGraph));
              ILGenerator il = baseBasePersist.GetILGenerator();
              il.Emit(OpCodes.Ldarg, 0);
              il.EmitCall(OpCodes.Call, fooA, null);
              il.Emit(OpCodes.Ret);
              baseBasePersist.Invoke(null, new object[] { Base });              
              …
       }
}

That is really bad thing, but sometimes it might be the only one way to solve clients issue. Use that way wise.

Have a nice development.

No comments: