4

As I know Expression trees is immutable so why compiler didn't use same object reference for a static expression, like string literals?

To clarify the question please see the example:

static void Main(string[] args)
{
    Test(p => true);//2637164
    Test(p => true);//3888474
    Test("true");//-292522067
    Test("true");//-292522067
    Console.ReadKey();
}

public static void Test(Expression<Func<string,bool>> exp)
{
    Console.WriteLine(exp.GetHashCode());
}

public static void Test(string str)
{
    Console.WriteLine(str.GetHashCode());
}
Reza ArabQaeni
  • 4,848
  • 27
  • 46
  • I'd say for one, it's _way_ easier to detect a duplicate string value than to detect a duplicate expression. Beyond that, questions of the form "why doesn't the compiler do X?" are generally not useful for Stack Overflow. It's not a _practical_ programming _problem_, and really the only answers you might get are going to be speculation. If by some miracle, the author of that feature in the compiler answers, the answer is almost certainly still going to be the same one for every other feature not in the compiler: "the benefit doesn't justify the cost." – Peter Duniho Nov 13 '17 at 06:34
  • 1
    @PeterDuniho: Well it's not *that* miraculous. That was one of my features. The spec says that the compiler is permitted but not required to make identical lambdas reference equal. As you surmise: that feature costs effort and the compiler team has better things to do than to write "optimizations" for situations that (1) never happen, and (2) can be optimized "by hand" easily enough if the developer so desires. – Eric Lippert Nov 13 '17 at 06:41
  • String literals are interned by the runtime, not the compiler. – Eric Lippert Nov 13 '17 at 06:42
  • @EricLippert: sorry if something I wrote implied a miracle would be required. That wasn't my intent. I recognize that it is possible to detect duplicate expressions. But compared to a simple string comparison, it's harder. Maybe I overstate it with the emphasis on the word "way", but I would still be surprised if it were literally as easy as a string comparison. Of course, you would know much better than I...feel free to correct me if it is basically the same work as a string comparison. :) – Peter Duniho Nov 13 '17 at 06:45
  • To the OP: I will also point out that your test does not actually demonstrate that the same object is used for string literals (even though it is). Two different string objects with the same value will have the same hash code. You have to look at the object reference itself to determine whether it's the same _object_. – Peter Duniho Nov 13 '17 at 06:46
  • 2
    @PeterDuniho: What I meant was: it is not really miraculous that the compiler developer answers the question. C# compiler developers answer questions all the time on SO. You said "if by some miracle the author of that feature answers" and then the author of that feature wrote an answer, so really, not a miracle. – Eric Lippert Nov 13 '17 at 06:47
  • 1
    @EricLippert: ah, yes...I see. I did use the word "miracle" there, didn't I? In my defense, my understanding is that the compiler team has a number of members; but you're the only person from that team I've seen _regularly_ answer questions (I've only seen others on rare occasions), and statistically speaking I figured the odds were low that you would have been the person who did that feature. So, it seems the miracle was granted! – Peter Duniho Nov 13 '17 at 06:49
  • 1
    @PeterDuniho: I'm sure you've seen Jared answer questions here; he's answered twice as many questions as I have. – Eric Lippert Nov 13 '17 at 06:53
  • _"he's answered twice as many questions as I have"_ -- really? I guess I'm not noticing them. You must have better name recognition. Well, I think I've worked on making this molehill into a mountain quite enough. I'll happily retract my use of the word "miracle"... :) – Peter Duniho Nov 13 '17 at 06:55
  • @PeterDuniho: No worries, I just thought it was funny, that's all. Also, I note that Jared never updated his SO bio when he joined the C# team proper, so it would be entirely reasonable to not realize that he's the compiler lead now. And Jason Malinowski also occasionally answers questions here. – Eric Lippert Nov 13 '17 at 06:59
  • Actually, I see why I'm not familiar with Jared's answers...I've only been active here a couple of years, and he's only posted four C# answers in that time...you've posted that many in the last few _days_ :). I barely have time to keep up with the C# tag, so if a post isn't under that tag, I'll pretty much never see it. Through my tunnel vision, you are _by far_ a more prolific answerer than he is. – Peter Duniho Nov 13 '17 at 07:01
  • 4
    @PeterDuniho: You make a convincing point. I hadn't realized his posting rate had dropped off so dramatically compared to the early days. I'll give him crap about it next time I see him. :-) – Eric Lippert Nov 13 '17 at 07:08
  • 1
    @PeterDuniho: my original question is a practical problem, after investigation i only get abstracted problem in a simple sample to easy understanding. We see many expression.Compile() in refactoring codes so we must change the design of codes or use a cached delegates that key is expression reference and value is compiled expression that is a delegate. So i find to ask a abstract question not to ask all of my problems. – Reza ArabQaeni Nov 13 '17 at 17:25
  • 1
    @RezaArabQaeni: If you are calling `Compile` on a lot of expression trees then why did you make them expression trees to begin with? If you need the lambdas to be delegates then why were they not converted to delegate types in the first place? – Eric Lippert Nov 13 '17 at 17:33
  • 2
    @RezaArabQaeni: And if you have an *actual problem that you need help with*, then why did you phrase it as a question about *my implementation choices twelve years ago?* Ask about the problem you actually have if you want help with that problem! Knowing the rationale for my choices in 2005 doesn't help you solve your real problem today. – Eric Lippert Nov 13 '17 at 17:37
  • @EricLippert: because expressions is used in linq queries and compiled expressions for in memory conditions. So to reduce the code that expression compiled directly. In couple of time this cause of memory leak. To reduce side effects I decided to use dictionary to cache delegates so i searching to find a unique key like expression object reference, then i curious to this question. Ask a real problem to illustrate completely get many energy because of my poor writing english. So i reduce range of problem to easy illustration. Thanks. – Reza ArabQaeni Nov 13 '17 at 18:36
  • @EricLippert The question is though why two syntactically equivalent things (lambda delegates and lambda expressions) are treated differently. Static lambda delegates are cached while static lambda expressions - not. If we change `Expression>` in OP sample to `Func` w/o changing anything else, the result (and the generated code) will be different. – Ivan Stoev Nov 13 '17 at 20:39
  • @IvanStoev: Two identical lambdas with distinct source code locations are always generated as not-reference-equal objects regardless of whether they're converted to delegates or expression trees; my understanding is that this fact is what the question was about. As for whether *individual* lambdas are cached in static fields or not, I do not recall if this varies depending on whether they are converted to expression trees or delegates. But I did not understand the question to be about that caching. – Eric Lippert Nov 13 '17 at 21:07
  • @IvanStoev: That is, we should expect `Func f1 = () => true; Func f2 = () => true; Console.WriteLine(object.ReferenceEquals(f1, f2));` to produce `False` regardless of whether these are delegates or `Expression>`. The compiler is *permitted* to produce a program that writes `True`, it just does not do this "optimization" because it's a lot of work for a very small win. – Eric Lippert Nov 13 '17 at 21:13
  • @IvanStoev: I think your question is about `public static Func DoIt() => () => true; ... Console.WriteLine(object.ReferenceEquals(DoIt(), DoIt()));`, which again, the compiler is *permitted* to produce a program that produces either `True` or `False`. In this case though, *sometimes* the compiler will choose to cache the lambda in a static field, and sometimes it will not. That's an implementation detail of the compiler and subject to change; the algorithm for deciding when to cache has changed a couple times in the last decade. – Eric Lippert Nov 13 '17 at 21:17
  • @IvanStoev: The former is a poor candidate for an optimization because its rare to have a large volume of source-code-identical lambdas; the latter is a good candidate for an optimization because collection pressure from allocation of many identical delegates in a loop is potentially common and costly. – Eric Lippert Nov 13 '17 at 21:19
  • @IvanStoev: Your observation that delegate lambdas are thus cached but expression lambdas are not is correct, and I do not recall the reasoning behind that decision. My point is simply that this is not, to my understanding, what the question was about. – Eric Lippert Nov 13 '17 at 21:21
  • @EricLippert Thanks for spending your time on this, appreciate! It was just curiosity due to similarity of the concepts. We live with what is given to us by the tool, know what it does and cache what and when needed :) – Ivan Stoev Nov 13 '17 at 21:42

2 Answers2

8

As I know Expression trees is immutable so why compiler didn't use same object reference for a static expression, like string literals?

The spec says that the compiler is permitted but not required to intern identical lambdas.

String literals are interned by the runtime for free; there's no cost to the compiler developer to do it.

The reason why I didn't intern expression trees is because every day we spent working on pointless unnecessary "optimizations" for unrealistic scenarios that actually save no valuable resource is a day that Visual Studio would have slipped its schedule. We spent that time on actual optimizations.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
0

Blindly guessing of what your actual issue is: instead of reference equality, use the answer from this question as the IEqualityComparer for your dictionary. It is obviously not as fast as Object.ReferenceEquals, but is definately faster than compiling the expression tree I would imagine.

MBoros
  • 1,090
  • 7
  • 19
  • i use this approach but not exactly, because if key of dictionary be a Expression may we keep a useless object in memory and GC can't clean taht. So i use a unique HashCode method that translate Expression to a string one to one. – Reza ArabQaeni Nov 15 '17 at 06:03
  • How do you do the tostring on the constants? Especially for not serializable objects? – MBoros Nov 16 '17 at 11:37
  • In my case expressions was property descriptor, so I get expression and translate that to entity full name plus property name. – Reza ArabQaeni Nov 17 '17 at 06:47
  • You mean it is not a generic solution, just for your usecase, right? – MBoros Nov 18 '17 at 13:28
  • Yes, the link that you introduced also is not generic solution it's just for property descriptor Expressions. – Reza ArabQaeni Nov 19 '17 at 10:13