Hi,
this is (again :-) question about API best practices. I apologize for its length, but I tried to formulate it including code snippets to allow to answer in a format [correct | incorrect] ;-)
One from important common NET best practices (and totally ignored by many developers) is: When IDisposable object is used, it should be always disposed as soon as possible explicitly (in code), not waiting for garbage collecting.
In MicroStation NET API, many classes, because they are wrappers around native classes (resources), implement IDisposable.
Therefore, my question is, what are “best practices IDisposable object patterns” in MicroStation CE NET API? Surprisingly, no example in MicroStation SDK uses Dispose, so there is suspicion the code is dirty and does not follow NET best practices. On the other hand, there are classes (at least one found, thanks Jean-Pierre Hundhausen for the notice) that logs when Dispose is ignored and not used properly.
Because the answer probably requires a good insight how API is implemented, I hope somebody (Robert Hook, Sunand Sandurkar?) will be able to forward this question to a proper person ;-)
As an input for the discussion about scenarios, I have thought about 3 different situations plus one extra question. Only situations where MicroStation NET API is involved, were investigated>
This scenario is simple in my opinion, because is like normal usage of IDisposable objects.
When e.g. Element (which is IDIsposable) is created, usually the code looks like this:
LineStringElement line = new LineStringElement(activeModel, null, points); line.AddToModel();
Warning CA2000 is correctly raised for this code by analyzer.
Am I right an explicit disposal is required here, so the correct code should be e.g. this one?
using (LineStringElement line = new LineStringElement(activeModel, null, points)) { line.AddToModel(); }
Many API methods return IDisposable objects, e.g. often used ElementAgenda instance. Example from ModifyFenceContentsTool.cs from SDK (some lines skipped):
FenceParameters fenceParams = new FenceParameters(modelRef, DTransform3d.Identity); ElementAgenda eAgenda = new ElementAgenda(); DgnModelRef[] modelRefList = new DgnModelRef[1]; FenceManager.BuildAgenda(fenceParams, eAgenda, modelRefList, false, false, false);
Here I assume the object ownership is passed to receiver (custom code), so ElementAgenda (and FenceParameters too) should be disposed by a developer? Again, SA2000 is raised by analyzer for the original version of the code. To solve the warnings, code can be changed to this one:
using (FenceParameters fenceParams = new FenceParameters(modelRef, DTransform3d.Identity)) using (ElementAgenda eAgenda = new ElementAgenda()) { DgnModelRef[] modelRefList = new DgnModelRef[1]; FenceManager.BuildAgenda(fenceParams, eAgenda, modelRefList, false, false, false); }
Correct?
This is not clear situation and many discussions can be found on Internet about this scenario (IDisposable as method parameter), because it depends how API is implemented.
Let’s discuss very simple code in custom DgnElementSetTool class:
public override StatusInt OnPreElementModify(Element element) { … }
My assumption here is that I am not an owner of the element (I do not create the instance), so I am not responsible to dispose the element and it will be disposed by state engine that calls my class from outside. Is it correct?
For the sake of completeness, in OnDataButton the situation is simpler (ModifyGroupedHoleTools.cs from SDK examples used):
protected override bool OnDataButton(Bentley.DgnPlatformNET.DgnButtonEvent ev) { Bentley.DgnPlatformNET.HitPath hitPath = DoLocate(ev, true, 1); … }
DgnButtonEvent is pure NET class (not implementing IDisposable), so it will be collected normally. And the rest of method is normal NET code, so in fact how hitPath is obtained is incorrect, because HitPath is inherited from DisplayPath that implements IDisposable, so it should be something like:
protected override bool OnDataButton(Bentley.DgnPlatformNET.DgnButtonEvent ev) { using (Bentley.DgnPlatformNET.HitPath hitPath = DoLocate(ev, true, 1)) { … } … }
This is the extra question: DgnTool class, which is a base class for primitive and modification tools, also implements IDisposable, so it should be disposed explicitly. But I think that using this standard code
public static void InstallNewInstance () { ModifyGroupedHoleTool modifyTool = new ModifyGroupedHoleTool(); modifyTool.InstallTool(); }
the instance ownership is passed from creator (the class in static method) to API, so MicroStation state engine is responsible to dispose it when another tool become active. Is it correct?
With regards,
Jan
Hi Jan,
I just wanted to let you know we asked development to review and provide some direction on this post.
Thank you,Bob
Robert Hook said:I just wanted to let you know we asked development to review
Thanks a lot! I am really looking forward to answers :-)
Similarly to C++ documentation, where - when it's necessary - it's explained who should release allocated memory, it would be helpful to have such information for IDisposable classes also in NET documentation. Or at least to have typical scenarios described.
Whereas IDisposable interface represents clear contract between class author and developer who use it (dispose this object as soon as possible), it's only general concept and not strict rule. Even in NET Framework API some IDisposable objects (e.g. some streams) are not intended to be disposed immediately. But in these cases, it's usually mentioned in the documentation and explained what is the best practice for the particular class.
Bentley Accredited Developer: iTwin Platform - AssociateLabyrinth Technology | dev.notes() | cad.point