Is "Access to modified closure" resolved by comprehension syntax? - c#

ReSharper 6.0 gives me the "Access to modified closure" warning for the dr identifier in the first code snippet.
private IEnumerable<string> GetTheDataTableStrings(DataTable dt) {
foreach (DataRow dr in dt.Rows) {
yield return GetStringFuncOutput(() => dr.ToString());
}
}
I think I have a basic understanding of what this warning is trying to protect me from: dr changes several times before GetTheDataTableStrings's output is interrogated, and so the caller might not get the output/behavior I expect.
But R# doesn't give me any warning for the second code snippet.
private IEnumerable<string> GetTheDataTableStrings(DataTable dt) {
return from DataRow dr in dt.Rows select GetStringFuncOutput(dr.ToString);
}
Is it safe for me to discard this warning/concern when using the comprehension syntax?
Other code:
string GetStringFuncOutput(Func<string> stringFunc) {
return stringFunc();
}

First off, you are correct to be concerned about the first version. Each delegate created by that lambda is closed over the same variable and therefore as that variable changes, the meaning of the query changes.
Second, FYI we are highly likely to fix this in the next version of C#; this is a major pain point for developers.
(UPDATE: This answer was written in 2011. We did in fact take the fix described below in C# 5.)
In the next version each time you run through the "foreach" loop we will generate a new loop variable rather than closing over the same variable every time. This is a "breaking" change but in the vast majority of cases the "break" will be fixing rather than causing bugs.
The "for" loop will not be changed.
See http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/ for details.
Third, there is no problem with the query comprehension version because there is no closed-over variable that is being modified. The query comprehension form is the same as if you'd said:
return dt.Rows.Select(dr=>GetStringFuncOutput(dr.ToString));
The lambda is not closed over any outer variable, so there is no variable to be modified accidentally.

The issue that Resharper is warning about has been resolved in both C# 5.0 and VB.Net 11.0. The following are extracts from the language specifications. Note that the specifications can be found in the following paths by default on a machine with Visual Studio 2012 installed.
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VB\Specifications\1033\Visual Basic Language Specification.docx
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC#\Specifications\1033\CSharp Language Specification.docx
C# Language Specification Version 5.0
8.8.4 The foreach statement
The placement of v inside the while loop is important for how it is captured by any anonymous function occurring in the embedded-statement.
For example:
int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
if (f == null) f = () => Console.WriteLine("First value: " + value);
}
f();
If v was declared outside of the while loop, it would be shared among all iterations, and its value after the for loop would be the final value, 13, which is what the invocation of f would print. Instead, because each iteration has its own variable v, the one captured by f in the first iteration will continue to hold the value 7, which is what will be printed. (Note: earlier versions of C# declared v outside of the while loop.)
The Microsoft Visual Basic Language Specification Version 11.0
10.9.3 For Each...Next Statements (Annotation)
There is a slight change in behavior between version 10.0 and 11.0 of the language. Prior to 11.0, a fresh iteration variable was not created for each iteration of the loop. This difference is observable only if the iteration variable is captured by a lambda or a LINQ expression which is then invoked after the loop.
Dim lambdas As New List(Of Action)
For Each x In {1,2,3}
lambdas.Add(Sub() Console.WriteLine(x)
Next
lambdas(0).Invoke()
lambdas(1).Invoke()
lambdas(2).Invoke()
Up to Visual Basic 10.0, this produced a warning at compile-time and printed "3" three times. That was because there was only a single variable "x" shared by all iterations of the loop, and all three lambdas captured the same "x", and by the time the lambdas were executed it then held the number 3.
As of Visual Basic 11.0, it prints "1, 2, 3". That is because each lambda captures a different variable "x".

Related

Specific of Closure Lambda in for and foreach c#

I read different articles regarding this theme Eric Lippert's blog and other here and know why this two code will work differently
int[] values = { 7, 9, 13 };
List<Action> f = new List<Action>();
foreach (var value in values)
f.Add( () => Console.WriteLine("foreach value: " + value));
foreach (var item in f)
item();
f = new List<Action>();
for (int i = 0; i < values.Length; i++)
f.Add(() => Console.WriteLine("for value: " + ((i < values.Length) ? values[i] : i)));
foreach (var item in f)
item();
But I didn't find clean explanation why eventually (begin from compiler c# version 5) was decided to made " the foreach loop variable will be logically inside the body of the loop, and therefore closures will get a fresh copy every time."
Because the old realization give a more freedom to use the closures Lambda ("by value or by ref") but demanded from programmer to use it carefully and if you needed the current implementation from foreach you should was used the following code:
foreach(var v in values)
{
var v2 = v;
funcs.Add( ()=>v2 );
}
QUESTIONS:
Q1. I wonder why in the end it was decided to change the implementation of foreach (pros and cons I read, it was 50/50 Eric Lippert's blog)?
Q2. In the case of a "for" loop, it's a value that falls outside the loop's operating range (this creates a very specific situation where the lambda gets a value that you'll never get in the loop), why is it "out of control" because it's a very error prone situation ? (question number 2 is more rhetorical therefore can be skipped)
Additional explanation (for Q1) It would be interesting to know the reasons why this implementation was chosen - why the developers of C#, starting with version 5, changed the principles of closure for the foreach loop. Or why they did it for the foreach loop but didn't do it for the for.
I wonder why in the end it was decided to change the implementation of foreach
Because (1) users strongly believed that the compiler's behaviour was at best unexpected, and at worst, simply wrong, and (2) there was no compelling reason to keep the strange behaviour. The biggest factor causing the design team to NOT take a breaking change is "because real code depends on the current behavior", but real code that depends on that behaviour is probably wrong!
This was a fairly easy call to make. Lots of people complained about the behaviour; no one at all complained about the fix. It was a good call.
why they did it for the foreach loop but didn't do it for the for.
(1) people didn't complain about the for loop, and (2) the change would be much more difficult, and (3) the change would be much more likely to produce a real-world break.
One reasonably expects that the "loop variable" of a foreach is not a "real" variable. You don't ever change it; the runtime changes it for you:
foreach(char c in "ABCDEFG")
{
c = 'X'; // This is illegal! You cannot treat c as a variable.
}
But that's not true of the loop variable(s) of a for loop; they really are variables.
for(int i = 0; i < 10; i += 1)
i = 11; // weird but legal!
for loops are much more complicated. You can have multiple variables, they can change value arbitrarily, they can be declared outside the loop, and so on. Better to not risk breaking someone by changing how those variables are treated inside the loop.

C# Accidental Tuple Use and Tuple Equality Use, Need Alternative (Beginner)

I am attempting to complete leetcode's problem #1266 (https://leetcode.com/problems/minimum-time-visiting-all-points/).
My solution works on Visual Studio just fine, but when I submit it to leetcode I encounter a compile time error :
Line 19: Char 12: error CS8320: Feature 'tuple equality' is not
available in C# 7.2. Please use language version 7.3 or greater. (in
Solution.cs)
(int, int) current = test[0];
(int, int) target = test.Last();
for (int i = 1; i < test.Count; i++)
{
target = test[i];
while (current != target) <<<<<<<<<<<<< ERROR LINE according to leetcode
I believe my VS2019 was up to date to begin with (I am now updating to the latest build : 16.3.9 --> 16.4.0) so I'm not sure what else I need to do on my end.
My question is : is there a simple alternative I could use or should I just move on? (As far as I'm concerned my solution passes - it completed the given test cases.)
Thanks for any time and help.
P.S. Sorry if you need more of my code, I'll include it if asked for. It's rather hacky and long, haha.
Instead of comparing tuples, you can compare tuples' properties:
...
// tuples current and target are not equal if either Item1 or Item2 are not equal
while (current.Item1 != target.Item1 || current.Item2 != target.Item2)
...
You may want explicit Tuple<int, int> syntax as well:
Tuple<int, int> current = test[0];
Tuple<int, int> target = test.Last();

Tracking method "bloat" over time with nDepend

We are looking at using nDepend to start tracking some of our technical debt, particularly around hard to maintain methods and cyclomatic complexity.
I believe this is possibly by taking a baseline report and then running a new analysis to provide the delta. Below is a very basic Powershell I've put together that does this.
$nDepend = "C:\_DEVELOPMENT\nDepend\NDepend.Console.exe"
$targetFile = "C:\_DEVELOPMENT\AssemblyToTest\CodeChallenge.Domain.ndproj"
$projectFolder = Split-Path -Path $targetFile
$outputFolder = "nDepend.Reports"
$previous = ""
Clear-Host
# See if we already have a .ndar file in the output folder, if we do back it up so we can do a comparison
if (Test-Path $projectFolder\$outputFolder\*.ndar)
{
Write-Output "Backing up previous NDAR report"
Copy-Item $projectFolder\$outputFolder\*.ndar $projectFolder\previous.ndar
$previous = ".\previous.ndar"
}
#The output path appears to be relative to the .ndproj file
& $nDepend $targetFile /Silent /OutDir .\$outputFolder /AnalysisResultToCompareWith .\previous.ndar
Here is the rule I've configured in nDepend: -
failif count > 1 bobs
from m in Methods
where m.NbLinesOfCode > 10
where m.WasChanged()
select new { m, m.NbLinesOfCode }
The goal of this is not to break the build if we have methods over 10 lines, but rather to break the build if somebody edits an existing method that is too big and does not improve it (or make it worse). However the where m.WasChanged() part of the rule isn't being triggered regardless of how much code I add. If I comment it out it will alert me that there are plenty of methods that exceed 10 lines, but I only want to know about recently changed ones.
Am I using the rule wrong? Or perhaps my powershell is incorrectly using the /AnalysisResultToCompareWith parameter?
There are default rules like Avoid making complex methods even more complex in the rule group Code Smells Regression that are close to what you want to achieve. You can get inspired by their source code.
The key is to retrieve the methods changed with...
m.IsPresentInBothBuilds() &&
m.CodeWasChanged() &&
and then compare metric evolution since baseline by accessing m.OlderVersion().
A ICompareContext reference two code bases snapshots the newer version and the older version. In this context the OlderVersion() extension method returns actually calls the ICompareContext.OlderVersion(codeElement), from the doc:
Returns the older version of the codeElement object.
If codeElement is already the older version, returns the codeElement object.
If codeElement has been added and has no corresponding older version, returns null.
This method has a constant time complexity.

CSC : error CS7038: Failed to emit module

After installing Visual Studio 2015 and building my project I receive the error
"CSC : error CS7038: Failed to emit module".
However my solution is building fine in Visual Studio 2013.
It is an ASP.NET webforms project .NET 4.0
Anyone?
UPDATE: it looks like the problem has to do with Red Gate Smart Assembly in combination with method parameters with default values.
UPDATE: Smart Assembly 6.9 fixes the error for me.
Original Snippet:
private void radButton1_Click(object sender, EventArgs e)
{
string perp = radTextBox1.Text;
int i = 0;
DataRow arp = ale.Rows[i];
while (i <= ale.Rows.Count)
{
if (ale.Rows[i].Field<>("FullName") = perp)
{
arp = ale.Rows[i];
ale.Rows.Remove(arp);
}
}
i = ale.Rows.Count;
radLabel1.Text = i.ToString();
}
Changed this:
if (ale.Rows[i].Field<>("FullName") = perp)
To This:
if (ale.Rows[i].Field<String>("FullName") == perp)
Got the same error (fresh installation of the VS2015 Enterprise, ASP.NET webforms project .NET 4.0).
After some investigation I've found that there are two DLLs in references which causes this. Both are .Net 2.0 assemblies and both of them obfuscated by Red Gate Smart Assembly 6.5. And the real reason is... obfuscation.
Luckily, these assemblies are mine, so I've tried to build them without using of Smart Assembly - error is gone.
Interesting is that no any errors or warnings shown by Visual Studio before trying to build a project.
Good luck!
EDIT: Updating Smart Assembly to version 6.9 fixed an issue.
As #Andrey reported this does appear to be an issue with obfuscated assemblies that is causing some difficulty for Roslyn. Today I was able to get a live repro of this error and the root cause appears to be the obfuscator is invalidating / corrupting how default parameter values are stored in metadata. When run through ildasm the default values will be displayed as:
.param [3] /* Invalid default value for 0800001F: */
The previous version of the compiler handled this scenario by treating the invalid value as null or default(T). We will be fixing Roslyn to have the same behavior.
I also had this exception being thrown in VB.NET (Visual Studio 2015 - Pro), and isolated a single line that was causing the error.
In the line of code below, if you define model as an integer, as in:
Dim model as Integer = 2
and then use:
Const N As Integer = model
you will throw the exception.
However, when I modified this to:
Dim N As Integer = model
the exception was not thrown. The Const syntax was legacy code from another program that I rapidly added model to, and constants can't be set to pre-defined integer types.
I just had "Failed to emit module". I mistakenly put empty brackets in a call to a generic extension method, only when inside a ternary operator, like this:
var a = b == 6 ? 8 : x.Foo<>();
(Outside of a ternary op, I just get regular error CS7003: Unexpected use of an unbound generic name.)
Extending on the answer from #jony-adamit:
I also had this compilation error being thrown in C# (Visual Studio 2015). It came down to the differences in compiler output from .NET 4.5 and Roslyn the new compiler in VS2015. You can take the below code and run it yourself or use dotnetfiddle (https://dotnetfiddle.net/fa2rMs) and switch between compilers.
In .NET 4.5 compiler (C# compiler version 12.0 or earlier) you get this message:
Compilation error (line 16, col 7): The type or namespace name
'Select' does not exist in the namespace 'Tools.Weapons' (are you
missing an assembly reference?)
In the Roslyn 1.0.0 and 1.1.0 compiler versions you get this message:
Compilation error (line 1, col 1): Failed to emit module
'MyAssembly'.
Code to reproduce the error (notice the namespace):
public class Program
{
public static void Main()
{
Tools.Delegater.Work();
}
}
namespace Tools
{
public static class Delegater
{
public static System.Action Work = () =>
{
var query = from x in Weapons
select x;
};
}
}
namespace Tools.Weapons
{
public class Cannon { }
}
As you can tell from the compiler messages, Roslyn's message leaves you guessing as to the what and the where about the compiler error. And depending on the size of the application, the lack of details could take days or weeks to discover the root cause. Whereas the message in the previous compiler pointed you to the exact spot to start reviewing your code.
Another big difference is the previous compiler will show you a syntax error in Visual Studio for this scenario, but unfortunately Roslyn (atm) does not. However, if you knew where to look and you hovered your mouse over the 'x' variable in the linq to sql statement, then you would see that Rosyln doesn't understand how to interpret it.
I was getting a similar error: "BC36970. Failed to emit module 'Something.dll'." I realize the error code is not the same but it is also a "failed to emit" type issue so I thought I'd share my answer.
Problem: My problem was that I had a string that was a constant but I was trying to append another string to it, like so (using VB code):
Dim MyString1 As String = "Test1"
Const MyString2 As String = "Test2" & MyString1
Solution: I simply had to convert the second string from Const to Dim and the error disappeared:
Dim MyString1 As String = "Test1"
Dim MyString2 As String = "Test2" & MyString1
I got this error when using a generic method but failing to include the type.
This code gave the error:
example.GetValue<>(foo);
Correction simply was to specify the type:
example.GetValue<string>(foo);
I'm not sure if the erroring example is valid syntax or not: I would have expected a syntax error if it is not rather than this error, and Intellisense accepted the syntax.
There's another bug that can cause this exact error:
Happened to me after changing a property name without refactoring, which caused some linq code to call the namespace itself!
To see the bug in action all you have to do is run this piece of code:
public static int Test()
{
var x = from c in ConsoleApplication5
select 3;
return x.First();
}
Where ConsoleApplication5 is the namespace!
This only happens in VS2015.
Update:
There's an issue for this now on GitHub if anyone's interested.
I got this error when I was working with data table and did not provide a data type where it was required:
bool temp = dtTab.AsEnumerable().Any(row => row.Field<>("active") == 1);
Giving it a proper data type got rid of the error. I also had to convert it to string to be able to compare it correctly-
bool temp = dtTab.AsEnumerable().Any(row => row.Field<Boolean>("active").toString() == "1");
I got this error too, seems like there is some major reasons that cause this error.
I posted this answer to wrap up mentioned solutions and help other guys so they can solve this error quickly
Using obfuscated DLLs by Smart Assembly mentioned on marked answer Solution: Update Smart Assembly to version 6.9
Namespace issue mentioned here, also has open Github issue Here
In my case the problem was this one, Field is strongly-typed, not specifying it, mentioned here
Using Const keyword, mentioned here
Please read all answers here on this page carefully, I'm sure you can solve the issue using one of mentioned solutions, good luck :)
This answer is similar to that posted by #wrtsvkrfm
'Dim NEWLINE As String = "<BR/>"
Dim NEWLINE As String = vbCrLf
Const HT_QTC_VENDOR As String = "<B>some stuff {0}</B>" & NEWLINE
This will cause the DLL to be not emitted,
Change the top 2 lines to
'Const NEWLINE As String = "<BR/>"
Const NEWLINE As String = vbCrLf
and the error goes away.
This should be a compilation error.
A CONST clearly cant depend on a variable, which may..., well..., vary. :-)
I get the same "Failed to emit module" error with this code but changing the "Const" to "Dim" resolves the problem:
Dim userStatus1 As String = convAttrSet("user_status")
Dim userStatus2 As String = convAttrSet("user_status")
Const PENDING As Boolean = (userStatus1 = userStatus2)
I got this error when using linq to entities and my linq query had a syntax error in the join clause
For me the issue occured after adding a postbuild step that changes the assemblyinfo.cs (NetRevisionTool.exe).
Take care to not change the AssemblyInfo.cs between building and Edit and continue.
I also had this exception being thrown in VB.NET (Visual Studio 2015 - Pro), and isolated a single line that was causing the error
Faulty....
Dim i As Short
Dim j As Short = BancoCombienElement(LaChaine)
Const a = (22 / 60) * j
Dim TiTxt As String = ""
Dim S_8_10_12_14 As String = ""
Work good!!!!!!!!
Dim i As Short
Dim j As Short = BancoCombienElement(LaChaine)
Dim a As Short = (22 / 60) * j
Dim TiTxt As String = ""
Dim S_8_10_12_14 As String = ""
Reason:
This error seems to be introduced when we have something weird in our code.
For me I made a mistake while getting the current user from identity.
Error Code:
int currentUserId = User.Identity.GetUserId<>();
Fixed Code:
Please specify the data type after GetUserID<---------->();In this case I have specified int.
Note
Please try to undo your code step by step so that you can find this issue. But otherwise it was very difficult for me track that error.
Thanks
This can happen for any error that the compiler did not catch. Probably
either the compiler doesn't know what IL to emit, or
it knows but in the process of actually doing it, something unexpected happens or
the IL that it emits is invalid and some safety net at a lower level than the compiler itself produces an error that the compiler is not built to handle.
In any case, this CS7038 occurs because the C# is valid and there is a good chance the compiler encountered an error in such an unexpected place that it is no longer able to trace it to any source code line that you wrote. Hence the seemingly random errors that people report here to cause this.
I've used C# for decades and I arrive here now because this is the first time ever I get an error like this.
In my case the following code generates the error. Use <LangVersion>Preview</LangVersion> in the project file and use the C# 8 compiler in Visual Studio 2019 preview.
void FailToEmitDll(ReadOnlySpan<char> span) {
ReadOnlySpan<char> temp;
temp = 1 switch
{
_ => span
};
}
Odd enough, if you change the declaration and the assignment to read like var temp instead, all works well.
Adding to the list of possible causes, in VS2019 with C# 8, I got the "Failed to emit module" message when doing something like:
_ = someBool ? SomeVoid() : default;
I filed a bug report about it here: https://github.com/dotnet/roslyn/issues/41741
I got this error when I run a complex expression in Mock object, I fixed this by store the expression result to local variable.

Altering object is affecting previous versions of object in a foreach loop

So, this one is pretty straight forward I think.
Here is my code:
Dictionary<int, IEnumerable<SelectListItem>> fileTypeListDict = new Dictionary<int, IEnumerable<SelectListItem>>();
foreach (PresentationFile pf in speakerAssignment.FKPresentation.PresentationFiles)
{
IEnumerable<SelectListItem> fileTypes = Enum.GetValues(typeof(PresentationFileType))
.Cast<PresentationFileType>().Select(x => new SelectListItem
{
Text = x.ToString(),
Value = Convert.ToString((int)x),
Selected = pf.Type == (int)x
});
fileTypeListDict.Add(pf.ID, fileTypes);
}
What is happening is that in the end the dictionary will have all the correct keys, but all of the values will be set to the fileTypes list created during the final iteration of the loop. I am sure this has something to do with the object being used as a reference, but have not seen this issue before in my time with C#. Anyone care to explain why this is happening and how I should resolve this issue?
Thanks!
This is the infamous "foreach capture issue", and is "fixed" in C# 5 ("fixed" is a strong word, since it suggests it was a "bug" before: in reality - the specification is now changed to acknowledge this as a common cause of confusion). In both cases, the lambda captures the variable pf, not "the value of pf during this iteration" - but in C# before-5 the variable pf is technically scoped outside the loop (so there is only one of them, period), where-as in C# 5 and above the variable is scoped inside the loop (so there is a different variable, for capture purposes, per iteration).
In C# 4, just cheat:
foreach (PresentationFile tmp in speakerAssignment.FKPresentation.PresentationFiles)
{
PresentationFile pf = tmp;
//... as before
now pf is scoped inside the foreach, and it will work OK. Without that, there is only one pf - and since you've deferred execution until the end, the value of the single pf will be the last iteration.
An alternative fix would be: don't defer execution:
fileTypeListDict.Add(pf.ID, fileTypes.ToList()); // note the ToList
Now it is evaluated while the pf is "current", so will have the results you expected.

Categories

Resources