I've noticed that I have this huge memory leak.My app relies heavily on infragistics ui components, and our code createsAnd destroys many UserControls that comprise mostly of UltraTextEditors.After some research I found that the constructor is not called on ANY control that contains an UltraTextEditor (and probably other editors as well).
This seemed so weird to me that I started a new project, that contains a main form, and a single user control, which in turn contains a single UltraTextEditor. I added a destructor to this control, and set it to print a message to the debug console.To the main control I added a single button that creates my user control and looses reference to it, and then a call to GC.Collect();.
If the control contains the UltraTextEditor, the descructor is never called, if I remove the line with "this.controls.add(ultraTextEditor1); " the destructor is called as expected.Why does this happen? Is there some setting I need to know of somewhere that prevents my control from being GCed? Does the UltraTextEditor save a reference to the parent class in some static place that never gets out of scope?
I'm not sure I completely follow this. Are you disposing the UserControl too? When you add the UltraTextEditor to the UserControl's Controls collection, the UserControl itself will hold onto the reference to the editor until the UserControl itself is disposed, which is why you're are seeing everything work when you remove 'this.controls.add...".
-Matt
i'll add my test's code example to clarify the test i'm running:
public class Test : UserControl { public Test() { Debug.WriteLine("Creating control"); this.Controls.Add(new UltraTextEditor()); //this.Controls.Add(new TextBox()); } ~Test() { Debug.WriteLine("Test destructor called on user control"); } } static class Program { private static void CreateTest() { Test test = new Test(); GC.Collect(); //request GC cycle } [STAThread] static void Main() { for (int i=0; i<10; i++) CreateTest(); Debug.WriteLine("Leaving test"); } }
this test outputs:
Creating controlCreating controlCreating controlCreating controlCreating controlCreating controlCreating controlCreating controlCreating controlCreating controlLeaving test
if we comment out the infragistics control and uncomment the TextBox we get:
Creating controlCreating controlCreating controlTest destructor called on user controlTest destructor called on user controlCreating controlTest destructor called on user controlCreating controlTest destructor called on user controlCreating controlTest destructor called on user controlCreating controlTest destructor called on user controlCreating controlTest destructor called on user controlCreating controlTest destructor called on user controlCreating controlLeaving test
-Yossi
Yossi,
What I believe is happening here is that the various Infragistics controls hook certain static events, such as XPThemeChanged, which will effectively root the control. If you do not explicitly dispose this control (or the Test object that you're creating), then these static events will not get unhooked, I do not think. However, the base Component class calls SuppressFinalize in its Dispose method, which means that you will not actually get a call to the finalizer when you dispose this control. Therefore you will never hit your destructor. Unless you have a very good reason though it's generally not a recommended .NET practice to use destructors and instead rely on the Dispose implementations.
Hi Matt,
i'v emailed infragistics support about this, and they say the same thing -> Infragistics control hook to static events.
1. The correct way to handle garbage collection in this case would have been to use weak references in the event, as in:
http://diditwith.net/PermaLink,guid,aacdb8ae-7baa-4423-a953-c18c1c7940ab.aspx
this will allow the objects to get collected , and their dispose method to be called implicitly by the distructor.
2. Regarding SupressFinalize, GC.SupressFinalize does not prevent the call to Dispose, but rather the redundent call to Dispose by the GC after Dispose have been called once already (the point of this function call is to prevent aggressive GC to cause rentries to the Dispose method).
As for the Control call to SupressFinalize, this call is done by the call to base.Dispose by the extending child, so in any case it's called only after the child's Dispose has executed.
3. Regarding best practices, destructors are not common and not widely used, however calling Dispose is exactly the kind of good reason you need for a destructor. it's exactly why microsoft implemented a call to Dispose in their destructor to Control.
When working in a managed environment, you expect your objects to get automatically garbage collected once you loose all references to them, this includes IDisposable objects. what i know as the best practice is to implement a destructor for all classes implementing the IDisposable interface.
as in microsoft's page on IDisposable interface : http://msdn.microsoft.com/en-us/library/system.idisposable.aspx
The IDisposable interface is there to allow you to release resource in case want to force the release of unmanaged resources (and allows you to release managed ones if you really want to) and dont want to wait for the GC to get to your object.
it was not intended (and generally not used) as a means of unregistering from static events.
From what I understand, destructors are not inherited by any derived classes, which would make any maintentance on framework that has several controls/components such as ours that all derive from a common type (such as ours with UltraControlBase and UltraComponentBase) extremely difficult, since every final exposed object would need to re-implement the destructor to then unhook from any static events. Furthermore, if any controls are added in the future, or static events on some base class, an effort would need to be made to go through every class throughout all of the assemblies and making sure that these events are properly unhooked; naturally, doing something like this would expose a much larger risk of future memory leaks than the current model of ensuring that a control/parent is disposed and everything is handled by the single base class. While I don't know what the design decision was regarding this matter, I'm sure that maintainability came into play here with such a large code base, and also with how the majority of users will be utilizing the controls. The link you provided regarding "weak delegates" is interesting, but would certainly be a non-trivial endeavor to implement, not to mention the possiblity for regression issues; you are welcome to submit a feature request for this, but I wouldn't really consider it a bug as much as a design decision.
As for your comment regarding SuppressFinalize, I am aware that it doesn't prevent the Dispose call from happening; in fact, it is explicitly called *after* the base Component class disposes itself. Before I made this comment, I ran a memory profiler to ensure that nothing was being rooted by our code, so even though the finalizer wasn't hit, nothing was rooted after disposing the UserControl; I didn't happen to see anything rooted even without disposing the user control either, but I'm not positive about this.
Well, Destructor cannot be inherited or overriden , but they are always called regardless of what inheriting classes do. but you dont have to re-implement it accross the inheritance graph.
http://msdn.microsoft.com/en-us/library/66x5fx1b(VS.80).aspx
the link above has an example and the output that demonstrates this. (C:B,B:A all have destructors, and all destructors are called)
The fact that Control always calls Dispose when it is GCed also demonstrates this fact (if you implement a UserControl and override the Dispose method, you will see that it gets called when the object is out of scope and GCed, even if you dont explicitly call it in your own destructor).
Therefore this issue's impact to your development effort would be zero, since as far as i recall, all your controls already inherit from Control, and thus the call to Dispose will be made if you use WeakEvents and allow objects to be garbage collected.
My porblem is that if this bug remains in your infrastructure then it forces the users to effectively treat your objects as unmanaged, and is almost exacly like calling "delete" for every "new" object in c++. this considerably increases the chances for memory leaks and lost objects for the users of the package, since C# developer relay on the managed framework to handle garbage collection.
Is there any chance this issue will be fixed?
Just saw your answer. Sorry about that.
I'll try it out my self :)
Having the same issues!
I must admit, like Pat H, that I can not recommend Infragistics to any companies as long as they have this issue.
Pat, did it work on 2008.3 as well, or??
Actually, this won't be necessary. I found the problem and the problem was ours not Infragistics. Since Matt double checked your source code and said the Dispose should be cleaning as expected, I went back into our code and looked much deeper than I initially did and found a case were one of our controls was creating an Infragistics control but not adding it to its' own Controls collection so when it was disposed of the child control (the Infragistics control) was not and that is what the memory profile screen shot was showing. When I saw that, I googled for this issue and found these forum entries and assumed my issue was related. While preventing a control's finalizer from being called and thus preventing the Dispose from being called is a problem, it is not what was leading to our issue. We are using CAB and so when a WorkItem is terminated, its' SmartParts are disposed of so I am not as concerned about having to manually call the Dispose on a control to get this clean up to happen since CAB will do this for us. Long story short, I am once again a happy customer. Although once I cleaned up this issue, I found what looks like a similar issue where we are leaving a UltraWinChart.IGWinTooltip.LocationChangedHookedControls hooked up. I assume we are not disposing of a Infragistics chart control here.
Thanks yossin. I will take a look and let you know how it goes.
pathennessy said:I agree. This is very disappointing and a serious design flaw. I won't be able to recommend their product suite to clients until this is resolved.
Have you contacted developer support regarding this issue? If you can duplicae the problem, you should Submit an incident to Infragistics Developer Support so they can get it corrected.