c# - Where predicates does not releases memory -


if include outside reference inside where predicate memory not releases.

let's have list<object> if write where predicate :

    list<object> mylist = new list<object>();     ...     mylist.add(object);     ...      expression<func<object,bool>> predicate = p => mylist.contains(p); 

even if make mylist = null or predicate = null, not releasing memory.

i have list<object> itemsource binded datagrid. make it's itemsource null, disposing datagrid, datagrid null. . have analyzed issue ants memory profiler 7.4. shows me because of wherepredicate holding reference.

if change wherepredicate in dispose(), memory getting released.

    expression<func<object,bool>> predicate = p => p.id == 0; 

that means removing reference in wherepredicate.

mmmh... interesting... expression<> cause closure... didn't know...

the end result: predicate doesn't have reference mylist

i'll explain:

private static bool isdebug() {     // taken http://stackoverflow.com/questions/2104099/c-sharp-if-then-directives-for-debug-vs-release     object[] customattributes = assembly.getexecutingassembly().getcustomattributes(typeof(debuggableattribute), false);      if ((customattributes != null) && (customattributes.length == 1))     {         debuggableattribute attribute = customattributes[0] debuggableattribute;         return (attribute.isjitoptimizerdisabled && attribute.isjittrackingenabled);     }      return false; }  static void main(string[] args) {     // check x86 or x64     console.writeline(intptr.size == 4 ? "x86" : "x64");      // check debug/release     console.writeline(isdebug() ? "debug, useless benchmark" : "release");      // check if debugger attached     console.writeline(system.diagnostics.debugger.isattached ? "debugger attached, useless benchmark!" : "debugger not attached");      console.writeline();      {         long memory = gc.gettotalmemory(true);          // big array, big enough can see allocation in         // memory         byte[] buffer = new byte[10000000];          console.writeline("just allocated array: {0}", gc.gettotalmemory(true) - memory);          // list<>, containing reference buffer         list<object> mylist = new list<object>();         mylist.add(buffer);          console.writeline("added list<>: {0}", gc.gettotalmemory(true) - memory);          // want sure buffer referenced @ least         // point         gc.keepalive(buffer);          // setting buffer = null useless, because         // list<> has anothe reference         buffer = null;         console.writeline("buffer = null: {0}", gc.gettotalmemory(true) - memory);          // if clear() list<>, last reference buffer         // removed, , buffer can freed         mylist.clear();         console.writeline("mylist.clear(): {0}", gc.gettotalmemory(true) - memory);          gc.keepalive(mylist);     }      console.writeline();     gc.collect();      {         long memory = gc.gettotalmemory(true);          // big array, big enough can see allocation in         // memory         byte[] buffer = new byte[10000000];          console.writeline("just allocated array: {0}", gc.gettotalmemory(true) - memory);          // list<>, containing reference buffer         list<object> mylist = new list<object>();         mylist.add(buffer);          console.writeline("added list<>: {0}", gc.gettotalmemory(true) - memory);          // want sure buffer referenced @ least         // point         gc.keepalive(buffer);          // setting buffer = null useless, because         // list<> has reference         buffer = null;         console.writeline("buffer = null: {0}", gc.gettotalmemory(true) - memory);          // want sure list<> referenced @ least         // point         gc.keepalive(mylist);          // if set null mylist, last reference mylist         // , buffer removed         mylist = null;         console.writeline("mylist = null: {0}", gc.gettotalmemory(true) - memory);     }      console.writeline();     gc.collect();      {         long memory = gc.gettotalmemory(true);          // big array, big enough can see allocation in         // memory         byte[] buffer = new byte[10000000];          console.writeline("just allocated array: {0}", gc.gettotalmemory(true) - memory);          // list<>, containing reference buffer         list<object> mylist = new list<object>();         mylist.add(buffer);          console.writeline("added list<>: {0}", gc.gettotalmemory(true) - memory);          // predicate, containing reference mylist         expression<func<object, bool>> predicate1 = p => mylist.contains(p);         console.writeline("created predicate p => mylist.contains(p): {0}", gc.gettotalmemory(true) - memory);          // second predicate, **not** containing reference         // mylist         expression<func<object, bool>> predicate2 = p => p.gethashcode() == 0;         console.writeline("created predicate p => p.gethashcode() == 0: {0}", gc.gettotalmemory(true) - memory);          // want sure buffer referenced @ least         // point         gc.keepalive(buffer);          // setting buffer = null useless, because         // list<> has reference         buffer = null;         console.writeline("buffer = null: {0}", gc.gettotalmemory(true) - memory);          // want sure list<> referenced @ least         // point         gc.keepalive(mylist);          // if set null mylist, interesting thing happens:         // memory freed, if predicate1 still alive!         mylist = null;         console.writeline("mylist = null: {0}", gc.gettotalmemory(true) - memory);          // want sure predicates referenced @          // least point         gc.keepalive(predicate1);         gc.keepalive(predicate2);          try         {             // compile predicate1             func<object, bool> fn = predicate1.compile();             // , execute it!             fn(5);         }         catch (nullreferenceexception)         {             console.writeline("predicate1 'pointing' null mylist");         }     } } 

this sample test in 3 parts: basic point big byte[] array allocated, , through checking how memory allocated check if array still allocated in way. it important code executed in release mode without debugger (ctrl+f5). if don't it, you'll warning when program starts

the first 2 "parts" show list<> keep "alive" items references (so byte[] in case), , freeing list<> or .clear()ing lets gc collect byte[].

the third part more interesting: there both list<> , expression<>... both seems keep reference byte[], illusion. expression<> written causes compiler generate "closure" around mylist<> variable. using ilspy quite easy see:

program.<>c__displayclassb <>c__displayclassb = new program.<>c__displayclassb(); <>c__displayclassb.mylist = new list<object>(); <>c__displayclassb.mylist.add(buffer3);  parameterexpression parameterexpression = expression.parameter(typeof(object), "p"); expression<func<object, bool>> predicate = expression.lambda<func<object, bool>>(expression.call(expression.field(expression.constant(<>c__displayclassb), fieldof(program.<>c__displayclassb.mylist)), methodof(list<object>.contains(!0)), new expression[] {     parameterexpression }), new parameterexpression[] {     parameterexpression }); 

(if don't have ilspy, can take @ code generated online compiler tryroslyn simpler sample)

an hidden class <>c__displayclassb generated compiler, field mylist. instead of having "local" variable mylist, method has local variable <>c__displayclassb has field mylist. predicate1 doesn't directly keep reference mylist, has reference variable <>c__displayclassb (see expression.constant(<>c__displayclassb)?), when

<>c__displayclassb.mylist = null; 

the predicate1 still has reference <>c__displayclassb, <>c__displayclassb.mylist null, there no more references mylist.


Comments