csharp_prefer_simple_using_statement / IDE0063 Not Appearing When Expected - c#

I am not seeing the code analysis rule csharp_prefer_simple_using_statement aka "Use simple 'using' statement (IDE0063)" produce output when expected. I added some dummy code to a method in my project, like so:
using (var file = Image.FromFile(userName))
{
System.Diagnostics.Debug.Assert(file != null);
}
My .sln-style Solution in VS 2022 includes several .csproj-style Projects (i.e. the "old way"). I have a .editorconfig file in the same folder as my .sln, and a variety of other built-in .NET analyzers and Roslynator analyzers work fine.
In the .editorconfig I have csharp_prefer_simple_using_statement = true:warning, and I
also added dotnet_diagnostic.IDE0063.severity = warning for good measure. I have double-checked that neither are duplicated elsewhere in the config, and there are no other .editorconfig files anywhere in the solution/project folders.
Even though I know it's supposed to be superseded by the .editorconfig file, I found the same setting in VS Options and enabled it there too:
And I also opened the project file (C# 10 / .NET 6 latest, btw), and set <AnalysisLevel>latest-recommended</AnalysisLevel>.
I have cleaned the build, restarted VS, and rebuilt, and I still see nothing in the Output, Error List, or in the editor indicating that it suggests simplifying the using statement. Again, I have many other code analysis rules that product output both live in the editor and in the build output & errors list.
Where am I going wrong, please?
EDIT: #Guru Stron's question tickled my spidey sense, and I discovered that while the first method here does not produce IDE0063, the latter does. Why?
public Stream GenerateReport()
{
using (var reportContext = new ReportRenderContext(this.ReportTemplate))
{
reportContext.Render();
}
return this.FileStream;
}
public static int GetAreaOfImage(string fileName)
{
using (var image = Image.FromFile(fileName))
{
return image.Size.Width * image.Size.Height;
}
}

using declaration works based on scope, resource will be disposed at the end of the scope, so the next one:
public Stream GenerateReport()
{
using (var reportContext = new ReportRenderContext(this.ReportTemplate))
{
reportContext.Render();
}
return this.FileStream;
}
Is not analogous to:
public Stream GenerateReport()
{
using var reportContext = new ReportRenderContext(this.ReportTemplate);
reportContext.Render();
return this.FileStream;
}
The latter one is analogous to:
public Stream GenerateReport()
{
using (var reportContext = new ReportRenderContext(this.ReportTemplate))
{
reportContext.Render();
return this.FileStream;
}
}
Which can have difference in some cases, so due to this compiler will not produce the warning (compiler is very smart and very dumb at the same moment, it does not "really" know what this.FileStream does. For example it can access the same resource as ReportRenderContext (like some file in non-shareable fashion) and disposing after return this.FileStream will introduce runtime error. Or this.FileStream can be just a relatively long operation and one of the main purposes of Dispose is to free resources as soon as they are not needed. There is "reverse" example when the 2nd snippet can fix a "bug" - in async context).

Related

Dynamic Update using the visual designer: is it possible, or better to convert flows to code?

Related to my earlier question about updating long-running flows with multiple child flows and custom activities, I'm working on implementing a plan as well as code for modifying workflows after they've been released to production.
Besides the various issues outlined in the other question, I'm trying to debug the visual designer appearing jumbled after prepping it for dynamic update. After running diffs on the .xaml, it appears to be an issue with the WorkflowViewState getting confused.
According to previous posts, it seems that abandoning the visual designer is one sure-fire way of solving this problem. However, I've found code that seems to nearly get me there. Here is how I'm saving the MainProcessFlow.xaml back to the file, after calling the DynamicUpdateServices.PrepareForUpdate method:
private static void SaveBuilderToFile(ActivityBuilder builder, string filePath)
{
// Make sure the activity builder has the right information about the c# expressions (even though its visual basic)
VisualBasic.SetSettings(builder, VisualBasic.GetSettings(builder));
// Set c# as the language
System.Activities.Presentation.Expressions.ExpressionActivityEditor.SetExpressionActivityEditor(builder, "C#");
// This is what I was hoping would correctly set the Viewstate
WorkflowViewState.SetViewStateManager(
builder.Implementation, WorkflowViewState.GetViewStateManager(builder.Implementation));
string fullPath = Path.GetFullPath(filePath);
using (FileStream file = File.Create(fullPath))
{
using (XmlWriter xmlWriter = XmlWriter.Create(file, new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }))
{
using (XamlWriter xamlWriter = ActivityXamlServices.CreateBuilderWriter( new XamlXmlWriter(xmlWriter, new XamlSchemaContext())))
{
XamlServices.Save(xamlWriter, builder);
}
}
}
}
After calling this method and opening the .xaml up in the visual designer, the activities are out of order. The flows are still "correct", in that the arrows are pointing in the right direction, but the layout is jumbled. After making my changes, creating the update map, and saving the .xaml back without the dynamicupdate information, the order is still incorrect. There is not very much documentation (that I can find) on setting the Viewstate in the xaml. Am I missing something?
Alternatively, is abandoning the visual designer a better option? We have nearly a years worth of Workflows, so it would be an immense undertaking, but getting DynamicUpdate working is a higher priority.
I just fixed such problem and I did it by taking WorkflowViewState.GetViewStateManager(builder.Implementation) before DynamicUpdateServices.PrepareForUpdate(builder). I don't know what is the problem, but after I PrepareForUpdate and the result from GetViewStateManager is null.
After I do all stuff, I pass the ViewStateManager to SaveBuilderToFile() and use it there.

Dynamically compiled project losing resources

I need to compile source code of big project dynamically and output type can be Windows Application or Class Library.
Code is nicely executed and its possible to make .dll or .exe files, but problem is that, when I'm trying to make .exe file - it's losing resources like project icon. Result file doesn't include assembly information to.
Any way to solve this? (Expected result should be the same, that manual Build function on project file in Visual Studio 2015).
Thank you!
var workspace = MSBuildWorkspace.Create();
//Locating project file that is WindowsApplication
var project = workspace.OpenProjectAsync(#"C:\RoslynTestProjectExe\RoslynTestProjectExe.csproj").Result;
var metadataReferences = project.MetadataReferences;
// removing all references
foreach (var reference in metadataReferences)
{
project = project.RemoveMetadataReference(reference);
}
//getting new path of dlls location and adding them to project
var param = CreateParamString(); //my own function that returns list of references
foreach (var par in param)
{
project = project.AddMetadataReference(MetadataReference.CreateFromFile(par));
}
//compiling
var projectCompilation = project.GetCompilationAsync().Result;
using (var stream = new MemoryStream())
{
var result = projectCompilation.Emit(stream);
if (result.Success)
{
/// Getting result
//writing exe file
using (var file = File.Create(Path.Combine(_buildPath, fileName)))
{
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(file);
}
}
}
We never really designed the workspace API to include all the information you need to emit like this; in particular when you're calling Emit there's an EmitOptions you can pass that includes, amongst other things, resource information. But we don't expose that information since this scenario wasn't hugely considered. We've done some of the work in the past to enable this but ultimately never merged it. You might wish to consider filing a bug so we officially have the request somewhere.
So what can you do? I think there's a few options. You might consider not using Roslyn at all but rather modifying the project file and building that with the MSBuild APIs. Unfortunately I don't know what you're ultimately trying to achieve here (it would help if you mentioned it), but there's a lot more than just the compiler invocation that is involved in building a project. Changing references potentially changes other things too.
It'd also be possible, of course, to update MSBuildWorkspace yourself to pass this through. If you were to modify the Roslyn code, you'll see we implement a series of interfaces named "ICscHostObject#" (where # is a number) and we get passed the information from MSBuild to that. It looks like we already stash that in the command line arguments, so you might be able to pass that to our command line parser and get the data back you need that way.

roslyn analyzers raising warnings which are later removed

I have an analyzer based on the default template for analyzers.
My problem is that when a do a full rebuild, some (but not all) the warnings appear on the error list.
When I open the files, the warnings start vanishing as the analyzer is re-executed on the open file. Eventually all the warnings disappear.
Am I registering these analyzers incorrectly. Ideally I only want them to execute once the code model is loaded properly.
Any suggestions on how to improve this would be great.
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(
this.HandleClassDeclaration,
SyntaxKind.ClassDeclaration);
}
This code analysers documentation (in this case) on the class declaration.
It reports a diagnosic when the Xml documentation nodes don't exist.
private void HandleClassDeclaration(SyntaxNodeAnalysisContext context)
{
// THE CHECK IN MY ANSWER BELOW GOES HERE...
var declaration = (ClassDeclarationSyntax)context.Node;
{
var hasDocumentation = declaration.HasDocumentation();
if (!hasDocumentation)
{
var diagnostic = Diagnostic.Create(this.Descriptor, declaration.Identifier.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
}
I am using this code to find the documentation.
public static DocumentationCommentTriviaSyntax GetDocumentationCommentTriviaSyntax(this SyntaxNode node)
{
if (node == null)
{
return null;
}
foreach (var leadingTrivia in node.GetLeadingTrivia())
{
var structure = leadingTrivia.GetStructure() as DocumentationCommentTriviaSyntax;
if (structure != null)
{
return structure;
}
}
return null;
}
can you switch your error list to build only view and see whether the warning is still there?
if warning is still there, that means the warning is generated by command line build. if the warning goes away once you opened the document, that means live analysis thinks there is no issue. and due to this difference between build and live analysis, the issue could happen.
if that is the case, it would be a bug in roslyn. (more specifically, bug in compilation option between live analysis and command line build - build inside of VS is also command line build with slightly different options)
For anyone else trying to analyse documentation in Roslyn, this little check is needed.
// <summary>
// check that the compiler is in a build mode that enables documentation analysis.
// it's not clear when this is off, but command line builds, and full rebuilds
// seem to have it turned off from time to time.
// </summary>
internal static bool IsDocumentationModeOn(this SyntaxNodeAnalysisContext context)
{
return context.Node.SyntaxTree?.Options.DocumentationMode
!= DocumentationMode.None;
}

Microsoft.Build.Evaluation.Project add folder and file force refresh of project in VS

I am creating an app that will add some files and folders to an existing project which is loaded in Visual studio. That works but it will always pop up a message telling the user (me) to refresh the project to show the new files.
When using Entity Framework and adding a migration it will add a file to a project that is currently loaded and it doesn't ask the user. I would like to be able to do the same thing.
Is this possible? If someone doesn't know the answer do they know how I might delve into EF and add migration to see how they do it?
Here is the code that I am using to edit the project file:
using Microsoft.Build.Evaluation;
internal class ProjectFileHandler
{
private void AddMigrationFolder(Project project, string projectFileLocation, string name)
{
var loc = Path.Combine(projectFileLocation.Substring(0, projectFileLocation.LastIndexOf("\\")), name);
project.AddItem("Folder", loc);
}
internal void AddMigrationFile(string projectfileLocation, string migrationFolderName, string fileLocation)
{
var project = new Project(projectfileLocation);
AddMigrationFolder(project, projectfileLocation, migrationFolderName);
project.AddItem("Compile", fileLocation);
project.Save();
}
}
Pretty old, but I'll leave a comment. Maybe it helps someone else.
The problem is the "project.Save();"
The solution recognizes this as an "external" change.
Try your code without this line.
Even older, but I had to solve this problem today, so for what it's worth, here's what I did.
Firstly, as far as I can tell, you can't update a project using Microsoft.Build.Evaluation without Visual Studio prompting the user to reload it. You have to call project.Save() to write your updates to the project XML, and Visual Studio 'sees' the file change and displays the prompt.
What you can do - and this looks to be what Entity Framework does, albeit with Powershell - is use EnvDTE instead of Microsoft.Build.Evaluation. EnvDTE is available on NuGet and the latest version supports .NET Framework 4.5 and .NET Standard 2, so it should be pretty widely compatible.
With EnvDTE you get a reference to a Project object and call AddFromFile(absoluteFilePath) - there are also other Add() methods if needed.
If you have the full file path to your project, you can get a project file using:
using System.Linq;
using System.Runtime.InteropServices;
using EnvDTE;
var dte = (DTE)Marshal.GetActiveObject("VisualStudio.DTE");
var project = dte
.Solution
.EnumerateProjects()
.First(p => p.FullName == fullPathToProject);
...where EnumerateProjects() is the following extension method:
using EnvDTE;
using System.Collections;
using System.Collections.Generic;
internal static class EnvDteExtensions
{
public static IEnumerable<Project> EnumerateProjects(
this Solution solution)
{
return Enumerate(solution.Projects);
}
private static IEnumerable<Project> Enumerate(IEnumerable items)
{
foreach (var item in items)
{
var candidateProject = item;
if (candidateProject is ProjectItem projectItem &&
projectItem.SubProject != null)
{
candidateProject = projectItem.SubProject;
}
if (!(candidateProject is Project project))
{
continue;
}
yield return project;
try
{
if (project.ProjectItems == null)
{
continue;
}
}
catch
{
continue;
}
foreach (var subProject in Enumerate(project.ProjectItems))
{
yield return subProject;
}
}
}
}
A Project in EnvDTE might actually be a project, or it might be a solution folder containing projects - the candidateProject is ProjectItem projectItem && projectItem.SubProject != null check handles the latter scenario.
EnvDTE's Project.AddFromFile() pops the file into Solution Explorer with no project reload prompt.
Finally, AddFromFile() seems to be idempotent, so you can spam the same file into the project without worrying if it already exists.

How can I use MSBuild to update version information only when an assembly has changed?

I have a requirement to install multiple web setup projects (using VS2005 and ASP.Net/C#) into the same virtual folder. The projects share some assembly references (the file systems are all structured to use the same 'bin' folder), making deployment of changes to those assemblies problematic since the MS installer will only overwrite assemblies if the currently installed version is older than the one in the MSI.
I'm not suggesting that the pessimistic installation scheme is wrong - only that it creates a problem in the environment I've been given to work with. Since there are a sizable number of common assemblies and a significant number of developers who might change a common assembly but forget to update its version number, trying to manage versioning manually will eventually lead to massive confusion at install time.
On the flip side of this issue, it's also important not to spontaneously update version numbers and replace all common assemblies with every install, since that could (temporarily at least) obscure cases where actual changes were made.
That said, what I'm looking for is a means to update assembly version information (preferably using MSBuild) only in cases where the assembly constituents (code modules, resources etc) has/have actually changed.
I've found a few references that are at least partially pertinent here (AssemblyInfo task on MSDN) and here (looks similar to what I need, but more than two years old and without a clear solution).
My team also uses TFS version control, so an automated solution should probably include a means by which the AssebmlyInfo can be checked out/in during the build.
Any help would be much appreciated.
Thanks in advance.
I cannot answer all your questions, as I don't have experience with TFS.
But I can recommend a better approach to use for updating your AssemblyInfo.cs files than using the AssemblyInfo task. That task appears to just recreate a standard AssemblyInfo file from scratch, and loses any custom portions you may have added.
For that reason, I suggest you look into the FileUpdate task, from the MSBuild Community Tasks project. It can look for specific content in a file and replace it, like this:
<FileUpdate
Files="$(WebDir)\Properties\AssemblyInfo.cs"
Regex="(\d+)\.(\d+)\.(\d+)\.(\d+)"
ReplacementText="$(Major).$(ServicePack).$(Build).$(Revision)"
Condition="'$(Configuration)' == 'Release'"
/>
There are several ways you can control the incrementing of the build number. Because I only want the build number to increment if the build is completely successful, I use a 2-step method:
read a number from a text file (the only thing in the file is the number) and add 1 without changing the file;
as a final step in the build process, if everything succeeded, save the incremented number back to the text file.
There are tasks such as ReadLinesFromFile, that can help you with this, but I found it easiest to write a small custom task:
using System;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace CredibleCustomBuildTasks
{
public class IncrementTask : Task
{
[Required]
public bool SaveChange { get; set; }
[Required]
public string IncrementFileName { get; set; }
[Output]
public int Increment { get; set; }
public override bool Execute()
{
if (File.Exists(IncrementFileName))
{
string lines = File.ReadAllText(IncrementFileName);
int result;
if(Int32.TryParse(lines, out result))
{
Increment = result + 1;
}
else
{
Log.LogError("Unable to parse integer in '{0}' (contents of {1})");
return false;
}
}
else
{
Increment = 1;
}
if (SaveChange)
{
File.Delete(IncrementFileName);
File.WriteAllText(IncrementFileName, Increment.ToString());
}
return true;
}
}
}
I use this before the FileUpdateTask to get the next build number:
<IncrementTask
IncrementFileName="$(BuildNumberFile)"
SaveChange="false">
<Output TaskParameter="Increment" PropertyName="Build" />
</IncrementTask>
and as my final step (before notifying others) in the build:
<IncrementTask
IncrementFileName="$(BuildNumberFile)"
SaveChange="true"
Condition="'$(Configuration)' == 'Release'" />
Your other question of how to update the version number only when source code has changed is highly dependent on your how your build process interacts with your source control. Normally, checking in source file changes should initiate a Continuous Integration build. That is the one to use to update the relevant version number.
I have written one custome task you can refer the code below. It will create an utility to which you can pass assemblyinfo path Major,minor and build number. you can modify it to get revision number. Since in my case this task was done by developer i used to search it and again replace whole string.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
namespace UpdateVersion
{
class SetVersion
{
static void Main(string[] args)
{
String FilePath = args[0];
String MajVersion=args[1];
String MinVersion = args[2];
String BuildNumber = args[3];
string RevisionNumber = null;
StreamReader Reader = File.OpenText(FilePath);
string contents = Reader.ReadToEnd();
Reader.Close();
MatchCollection match = Regex.Matches(contents, #"\[assembly: AssemblyVersion\("".*""\)\]", RegexOptions.IgnoreCase);
if (match[0].Value != null)
{
string strRevisionNumber = match[0].Value;
RevisionNumber = strRevisionNumber.Substring(strRevisionNumber.LastIndexOf(".") + 1, (strRevisionNumber.LastIndexOf("\"")-1) - strRevisionNumber.LastIndexOf("."));
String replaceWithText = String.Format("[assembly: AssemblyVersion(\"{0}.{1}.{2}.{3}\")]", MajVersion, MinVersion, BuildNumber, RevisionNumber);
string newText = Regex.Replace(contents, #"\[assembly: AssemblyVersion\("".*""\)\]", replaceWithText);
StreamWriter writer = new StreamWriter(FilePath, false);
writer.Write(newText);
writer.Close();
}
else
{
Console.WriteLine("No matching values found");
}
}
}
}
I hate to say this but it seems that you may be doing it wrongly. Is much easier if you do generate the assembly versions on the fly instead of trying to patch them.
Take a look at https://sbarnea.com/articles/easy-windows-build-versioning/
Why I do think you are doing it wrong?
* A build should not modify the version number
* if you build the same changeset twice you should get the same build numbers
* if you put build number inside what microsoft calls build number (proper naming would be PATCH level) you will eventually reach the 65535 limitation.

Categories