-
Notifications
You must be signed in to change notification settings - Fork 703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes #3521 - DimAuto
equality broken
#3649
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion for a simpler, cleaner, and more robust implementation in the long comment in this review.
[Fact]
public void DimFunc_Equal ()
{
Func<int> f1 = () => 0;
Func<int> f2 = () => 0;
Dim dim1 = Func (f1);
Dim dim2 = Func (f2);
Assert.Equal (dim1, dim2);
f2 = () => 1;
dim2 = Func (f2);
Assert.NotEqual (dim1, dim2);
} After switching to I think it was bogus before. [Fact]
public void DimFunc_Equal ()
{
Func<int> f1 = () => 0;
Func<int> f2 = () => 0;
Dim dim1 = Func (f1);
Dim dim2 = Func (f1);
Assert.Equal (dim1, dim2);
dim2 = Func (f2);
Assert.NotEqual (dim1, dim2);
f2 = () => 1;
dim2 = Func (f2);
Assert.NotEqual (dim1, dim2);
} I think this test is correct. Correct? |
Will take a look shortly, when I'm at my PC again. |
💯
Looks better, yes. Just for reference, here is how equality of delegates works:
Aside from that, I think I need to pull the branch and take a look at a couple of other things about the code. One thing is that the use of |
Also, the whole delegate equality thing got me thinking... I want to be sure your intent is aligned with what this code will do, or at least allow. Remember that all delegates you can write in dotnet are MulticastDelegate, which is a mutable reference type which contains an invocation list. But it's also a special type treated interestingly by the compiler in potentially unexpected ways, because of when they're evaluated. Take this simple program for example, which showcases both delegate fun and record behavior (immutable value by default): https://dotnetfiddle.net/wYactJ The reason two of those instances are "pointless" is because SimplePositionalRecord, PositionalRecordWithImmutableProperty, and PositionalRecordWithImmutablePropertyBackedByReadOnlyField are exactly the same thing. Note how we can append to the one in mutable, and the method is added. Yet, even though they're all given a reference to the same delegate instance, none of them affect each other, which the second two sets of invocations demonstrate. The delegate is created and evaluated at time of instantiation. That adds some color to the delegate equality rules above, but it still isn't the whole story. Extra unnecessary babbling that is safe to ignoreIL-wise, the delegate instances that are allocated by the
And the methods they each refer to, respectively, are:
If we hadn't used Action, and instead assigned a signature-compatible custom delegate we created, we'd have type casts AND no equality in the IL, thanks to the extra classes for the delegates. But it's legal to do that. If you use lambdas or other anonymous delegates, it all gets even messier, as those each create whole-ass classes, too, also created and evaluated at instantiation, not invocation (which is actually exactly what creates the closure capture problem you may have seen VS warn you of sometimes). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a couple of minor comments about explicit properties in records not being needed when they're already defined in the primary constructor.
Also, it's been an absolute hell of a couple of weeks... Things are clearing up, though, so I'm gonna have more time and desire to work on TG and/or TG-connected stuff over the next few days, finally. 🥳 |
Sorry to hear. Life can be hard sometimes. Thanks for looking at this. Learned a bunch. |
Excellent! That was the hope. :) It's an under-utilized feature, but a really handy one (records, I mean). They can save you lots of effort and give some consistency to implementation that you no longer have to do yourself. I love 'em when they can be used. Since all record types have value semantics out of the box (unless you manually do bad things to break it), record classes, in particular, have the neat property of enabling treatment of them as value types, but they're still reference types, which can be useful in certain scenarios. These can and should at some point be turned into
The reason I wouldn't say just do it now is because it would be smart to add Yeah. Life gets interesting when perfect storms of vendor failures plus deadlines plus otherwise innocent changes that happen to coincide time-wise with some of those failures come together to make a ton of extra work. But hey - job security, I guess? 😅 |
Oh and more record use info: If you want a property declared in the primary constructor to have a default value, like a field initializer, the standard optional parameter syntax is how you do it. It gets you both initialization with no need for callers to supply all parameters, as well as explicit construction without the initialization happening, all in the same line. |
And you can always (and it's a good idea to) add the ReSharper will suggest it for you, so you can add it with a click, rather than writing it yourself.
|
Sent you tig#38 as a quick suggestion, primarily because of AoT. |
There are subtle reasons for this. Regardless, it will be abstract when it's an interface, anyway, so no harm in doing it now.
Just an extra sanity check/guard
Not the final form. Just showing steps.
Fixes
Proposed Changes/Todos
Pull Request checklist:
CTRL-K-D
to automatically reformat your files before committing.dotnet test
before commit///
style comments)