This is due to caching performed by the code generated by the compiler to access the dynamic property. (The analysis is performed with the outputs of VS2015 and .NET 4.6; other versions of the compiler may produce different output.)
Call str = foo.SomePropName; is rewritten by the compiler into something like this (according to dotPeek, note that <>o__0 , etc. are tokens that are not legal C #, but created by the C # compiler):
if (Program.<>o__0.<>p__2 == null) { Program.<>o__0.<>p__2 = CallSite<Func<CallSite, object, string>>.Create(Binder.Convert(CSharpBinderFlags.None, typeof (string), typeof (Program))); } Func<CallSite, object, string> target1 = Program.<>o__0.<>p__2.Target; CallSite<Func<CallSite, object, string>> p2 = Program.<>o__0.<>p__2; if (Program.<>o__0.<>p__1 == null) { Program.<>o__0.<>p__1 = CallSite<Func<CallSite, object, object>>.Create(Binder.GetMember(CSharpBinderFlags.None, "SomePropName", typeof (Program), (IEnumerable<CSharpArgumentInfo>) new CSharpArgumentInfo[1] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null) })); } object obj3 = Program.<>o__0.<>p__1.Target((CallSite) Program.<>o__0.<>p__1, obj1); string str1 = target1((CallSite) p2, obj3);
Program.<>o__0.<>p__1 is a static field (for a private nested class) of type CallSite<Func<CallSite,object,object>> . It contains a link to a dynamic method that was compiled on demand the first time it accessed foo.SomePropName . (Presumably this is due to the fact that the creation of the binding is slow, so caching provides a significant increase in speed on subsequent accesses.)
This DynamicMethod contains a link to DynamicILGenerator , which refers to DynamicScope , which eventually contains a list of tokens. One of these tokens is the dynamically generated string 'System.Dynamic.ExpandoObject' does not contain a definition for 'SomePropName' . This line exists in memory, so that dynamically generated code can raise (and catch) a RuntimeBinderException using the "correct" message.
In general, the <>p__1 contains about 2K data (including 172 bytes for this string). There is no supported way to free this data, since it is rooted in a static field in the type generated by the compiler. (Of course, you could use reflection to set this static field to null , but it will be extremely dependent on the implementation details of the current compiler and will most likely be broken in the future.)
From what I have seen so far, it seems that using dynamic allocates about 2 Kbytes of memory to access properties in C # code; you probably should just consider this at the cost of using dynamic code. However (at least in this simplified example) this memory is allocated only when the code is first run, so it should not continue to use more memory the longer the program runs; another leak may occur that pushes the working set up to 5 GB. (There are three line instances because there are three separate lines of code that execute foo.SomePropName , however there will still be only three instances if you call foocall 100 times.)
To improve performance and reduce memory usage, you may need to use Dictionary<string, string> or Dictionary<string, object> as a simpler storage of keys / values ββ(if possible using a way to write code). Note that ExpandoObject implements IDictionary<string, object> , so the following small one overwrites the same output, but avoids the overhead of dynamic code:
public static string foocall() { string str = "", str2 = "", str3 = ""; // use IDictionary instead of dynamic to access properties by name IDictionary<string, object> foo = new ExpandoObject(); foo["SomePropName"] = "a test value"; Console.Write("step 1?"); var s2 = Console.ReadLine(); // have to explicitly cast the result here instead of having the compiler do it for you (with dynamic) str = (string) foo["SomePropName"]; Console.Write("step 2?"); s2 = Console.ReadLine(); str2 = (string) foo["SomePropName"]; Console.Write("step 3?"); s2 = Console.ReadLine(); str3 = (string) foo["SomePropName"]; return str; }