[AutoConstructor] in the code below will automatically generate a constructor (as shown in the figure below):
It works fine in Visual Studio, but JetBrains Rider has an error message:
I do not understand. . .
(Because I am not good at English, I am using Google Translate to ask questions, please forgive me)
using System;
using System.Linq;
using ComputeSharp;
namespace ComputeSharpTest
{
class Program
{
static void Main(string[] args)
{
// Allocate a writeable buffer on the GPU, with the contents of the array
// Get some sample data
int[] array = Enumerable.Range(1, 1000000).ToArray();
// Allocate a GPU buffer and copy the data to it.
// We want the shader to modify the items in-place, so we
// can allocate a single read-write buffer to work on.
using ReadWriteBuffer<int> buffer = Gpu.Default.AllocateReadWriteBuffer(array);
// Launch the shader
Gpu.Default.For(buffer.Length, new MultiplyByTwo(buffer));
// Get the data back
buffer.CopyTo(array);
}
}
[AutoConstructor]
public readonly partial struct MultiplyByTwo : IComputeShader
{
public readonly ReadWriteBuffer<int> buffer;
public void Execute()
{
buffer[ThreadIds.X] *= 2;
}
}
}
As per https://github.com/Sergio0694/ComputeSharp/issues/116 (and also https://github.com/Sergio0694/ComputeSharp#requirements):
In order to work correctly, ComputeSharp also needs the source
generator to be added to consuming projects as an analyzer, so that it
can run when the code is being compiled.
You can do so by adding the following code to your .csproj file, just
like in the sample projects:
<ItemGroup>
<ProjectReference Include="..\..\src\ComputeSharp\ComputeSharp.csproj" />
<ProjectReference Include="..\..\src\ComputeSharp.SourceGenerators\ComputeSharp.SourceGenerators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"
PrivateAssets="contentfiles;build" />
</ItemGroup>
Additionally, you need to ensure you are using .NET 5 (not an early version of .NET Framework / Core).
If your issue persists, you may also wish to try using VS 2019 rather than Rider.
Related
I've been wrestling with Source Generators but there's a lack of tutorials and information that are hurting.
I want to generate some C# classes from a database. Using a T4 template to do this is difficult and problematic, because of issues I'm having with using SQL in T4 templates and similar.
The description of Source Generator is that "The generator can create new C# source files on the fly that are added to the user's compilation. In this way, you have code that runs during compilation. It inspects your program to produce additional source files that are compiled together with the rest of your code." which seems to match what I want.
This seems significantly more promising.
I've created a project, and put a [Generator] into it using this tutorial.
Full source:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace x
{
[Generator]
internal class TestGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
File.Create(#"C:\temp\ITRUNS.TXT");
var sourceBuilder = new StringBuilder(#"
using System;
namespace HelloWorldGenerated
{
public static class HelloWorld
{
public static void SayHello()
{
Console.WriteLine(""Hello from generated code!"");
Console.WriteLine(""The following syntax trees existed in the compilation that created this program:"");
");
Debugger.Launch();
// using the context, get a list of syntax trees in the users compilation
var syntaxTrees = context.Compilation.SyntaxTrees;
// add the filepath of each tree to the class we're building
foreach (SyntaxTree tree in syntaxTrees)
{
sourceBuilder.AppendLine($#"Console.WriteLine(#"" - {tree.FilePath}"");");
}
// finish creating the source to inject
sourceBuilder.Append(#"
}
}
}");
// inject the created source into the users compilation
context.AddSource("helloWorldGenerator", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
}
public void Initialize(GeneratorInitializationContext context)
{
}
}
}
I want this to run when I build the solution and create some .cs files
However, it does not run. I put some code in the execute method to create a file, and it does not create the file.
The tutorial says:
Add the source generator from a project as an analyzer and add preview to the LangVersion to the project file like this:
I don't really know what this means. Which project? I tried to download the samples as suggested, but I can't get them to build.
When I examine the code, it's quite hard to understand what they've done that is different.
And what they say in the tutorial about adding the analyzer, doesn't seem to present anywhere in the sample code!
I tried adding the project reference in the csproj that it said to add, however that didn't work - it did create an item within Analyzers but it just has a red - and says 'Ignored' on the tooltip.
I honestly don't know what else I can do to figure out how to get this to work.
I also don't know for sure if it will do what I want - autogenerate a bunch of .cs files with code in that I can use.
I'm going to give it one more go then I'll just write a console application to do it manually instead. Any ideas are welcome.
Is there any way to print or use in anyway an actual code piece or snip? Like if the code is-
if(BA == True)
{
Console.Writeline("Okay")
};
Then if it's possible to actually print that entire text "if...} ;" to the console or excel / word file etc.. so the console output will be-
if(BA == True)
{
Console.Writeline("Okay")
};
for example.. Sounds like very simple and basic if possible at all, but i couldn't find it anywhere with many search combos..
Thanks..
As far as I know there's not really an easy way to do this in C#. There are a couple ways around it:
Distribute your source with your program in a known location, then in the code open the source file and read out the contents parsing it for the parts you need.
Distribute a "prepared" version of source, saving only the parts you need in some file, possibly formatted using JSON or something to easily get the important bits. This preparation could be done as part of the build process.
On the cooler yet slightly more ridiculous side of things, C# code is normally compiled to IL, which can very effectively be decompiled (for example, in ILSpy). It might just be that there's a decompilation library out there you can use on the source of the running program, and then there's no need to also package a source file. Note that this won't have great results if your build process has obfuscation. If that's the case, you'll also need to put the decompiled code through a deobfuscator that works with your obfuscator before outputting it to the user.
Out of curiosity, why do you want to do this? I think I tried to find a way to do it too a while back but for the life of me can't remember why.
Yes, that's possible in 8-20 lines of code.
Here's the code:
using System;
using System.IO;
using System.Reflection;
namespace CodeViewer
{
internal class Program
{
public static void Main(string[] args)
{
Console.WriteLine(Read());
}
public static string Read()
{
string res = "";
Assembly a = Assembly.GetExecutingAssembly();
Stream stream = a.GetManifestResourceStream("CodeViewer.Program2.cs");
StreamReader reader = new StreamReader(stream);
res = reader.ReadToEnd();
return res;
}
}
}
CodeViewer is the namespace of the project.
Here's a necessary part of the project's file (.csproj):
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Program.cs">
<Link>Program2.cs</Link>
</EmbeddedResource>
</ItemGroup>
I added the link to the Program.cs where is my program's code. After, I accessed it in Read method.
Just switched to VS2022, created new project and see this:
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
Where is all other stuff? Why is that by default now?
Click the link. It redirects to https://learn.microsoft.com/nl-nl/dotnet/core/tutorials/top-level-templates. It has a paragraph stating:
If you want to use the old templates, see the Use the old program style section.
That section mentions that this is the new default. To circumvent it, create a .NET 5-targeting application, and modify your project file:
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
A workaround I guess would be to create a custom project template.
If you are using Visual Studio, you can install the Classic Console Template
https://marketplace.visualstudio.com/items?itemName=Doomdied.ClassicConsole1
It add old classic console back, then you can forgot the new one.
You can access the args through a special variable with that name in a top level statement class file:
if (args.Length > 0)
{
foreach (var arg in args)
{
Console.WriteLine($"Argument={arg}");
}
}
else
{
Console.WriteLine("No arguments");
}
Similarly, you just return an int to set the exit code:
string? s = Console.ReadLine();
int returnValue = int.Parse(s ?? "-1");
return returnValue;
See:
https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/top-level-statements
As to why, there has been a push to clean up the source files from needless whitespace, masses of curly braces, long lists of imports at the top of each file and the explicit namespace declaration, where most everyone syncs the namespaces with the assembly name and solution folder anyway.
It's been a thorn in the eye of many that simple things in c# need 10s of lines of code where they are a single line in node or python or ruby. It's just not productive. Same for Razor templates and Razor files. You just need an IDE to do she right thing. With these changes it should become much easier to be productive from the GitHub, even if you're not using Visual Studio.
Visual Studio 2022 with .NET 6 uses a new template when creating a C# console app. The new template reduces the amount of boilerplate code necessary to write a simple C# program. I believe this change was meant to benefit beginning programmers and those who are new to C#.
New style:
Statements in Program.cs that appear outside of any function are automatically placed in Main().
Function declarations are moved outside Main() and made static.
A number of using statements are implicitly added for common namespaces like System, System.IO, System.Linq, etc.
Example:
// New style
Console.WriteLine("Code in Main()");
Test();
void Test()
{
Console.WriteLine("Test");
}
is roughly equivalent to:
// Old style
using System;
namespace MyApp
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Code in Main()");
Test();
}
static void Test()
{
Console.WriteLine("Test");
}
}
}
There is nothing stopping you from using the old style. You can copy and paste the old-style code above into Program.cs, and it will run just fine.
There is an option now, to disable top-level statements:
I just tried it and it produces an old-style project. :-)
You also don't loose file-scoped namespaces, they still work.
The "other stuff" is all there; it's just a new convention. It isn't obvious as you might use to get things in C# but this is the new way. And of course, you can use the old style with Main(string args) by selecting Do no use top-level statements checkbox when creating the project in Visual Studio or employ the --use-program-main option on the command line, like this:
dotnet new console --use-program-main
As for the magic of accessing the command line arguments with this new style, see my answer here.
I am currently building a tool which will support the development of an ASP.NET Core project. This tool uses the Roslyn APIs and other methods for verifying some development requirements (such as project-specific attributes being applied on API Controllers, enforcing naming conventions, and generating some source code for the JavaScript SPA which accesses an API written using the ASP.NET Core Web API template).
In order to do that, I am currently using hardcoded paths to generate code for the SPA app. But in the app's *.csproj file there is actually a "SpaRoot" property specifying where the SPA application is located inside the project:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
...
</PropertyGroup>
...
</Project>
My question is: how can I read the "SpaRoot" property's value using the Roslyn APIs?
I have written a minimum code sample to create a Workspace, open the Solution, and retrieve the Project's reference, which resembles the following:
static async Task Main(string[] args)
{
string solutionFile = #"C:\Test\my-solution.sln";
using (var workspace = MSBuildWorkspace.Create())
{
var solution = await workspace.OpenSolutionAsync(solutionFile);
string projectName = "some-project";
var project = solution.Projects.Single(p => p.Name == projectName);
// How to extract the value of "SpaRoot" from the Project here?
}
I've tried searching on how to extract the "SpaRoot" property from the Project reference, and even went as far as debugging to see if I could spot a way myself. Unfortunately, I came up with no answers to that, and I'm still using hardcoded paths in my original code.
Is it even possible to retrieve the value of .csproj properties of a Project using the current Roslyn APIs?
This is more difficult that you would think :) The Roslyn apis only know what the compiler knows and the compiler is not going to be given anything regarding the SpaRoot property. We can use the MSBuild apis to figure this out though. specifically the Microsoft.Build.Evaluation.Project class.
Some assumptions I am making
You only want to examine .NET Core projects
You will have the .NET Core SDK installed on which ever system runs this tool
So first we want a project file that looks like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<!--NOTE: If the project you are analyzing is .NET Core then the commandline tool must be as well.
.NET Framework console apps cannot load .NET Core MSBuild assemblies which is required
for what we want to do.-->
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>Latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<!-- NOTE: We put ExcludeAssets="runtime" on all direct MSBuild references so that we pick up whatever
version is being used by the .NET SDK instead. This is accomplished with the Microsoft.Build.Locator
referenced further below. -->
<PackageReference Include="Microsoft.Build" Version="16.4.0" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="2.9.8" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="3.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="3.4.0" />
<!-- NOTE: A lot of MSBuild tasks that we are going to load in order to analyze a project file will implicitly
load build tasks that will require Newtonsoft.Json version 9. Since there is no way for us to ambiently
pick these dependencies up like with MSBuild assemblies we explicitly reference it here. -->
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
</ItemGroup>
</Project>
and a Program.cs file that looks like this:
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.MSBuild;
// I use this so I don't get confused with the Roslyn Project type
using MSBuildProject = Microsoft.Build.Evaluation.Project;
namespace loadProject {
class Program {
static async Task Main(string[] args) {
MSBuildWorkspaceSetup();
// NOTE: we need to make sure we call MSBuildLocator.RegisterInstance
// before we ask the CLR to load any MSBuild types. Therefore we moved
// the code that uses MSBuild types to its own method (instead of being in
// Main) so the CLR is not forced to load them on startup.
await DoAnalysisAsync(args[0]);
}
private static async Task DoAnalysisAsync(string solutionPath) {
using var workspace = MSBuildWorkspace.Create();
// Print message for WorkspaceFailed event to help diagnosing project load failures.
workspace.WorkspaceFailed += (o, e) => Console.WriteLine(e.Diagnostic.Message);
Console.WriteLine($"Loading solution '{solutionPath}'");
// Attach progress reporter so we print projects as they are loaded.
var solution = await workspace.OpenSolutionAsync(solutionPath, new ConsoleProgressReporter());
Console.WriteLine($"Finished loading solution '{solutionPath}'");
// We just select the first project as a demo
// you will want to use your own logic here
var project = solution.Projects.First();
// Now we use the MSBuild apis to load and evaluate our project file
using var xmlReader = XmlReader.Create(File.OpenRead(project.FilePath));
ProjectRootElement root = ProjectRootElement.Create(xmlReader, new ProjectCollection(), preserveFormatting: true);
MSBuildProject msbuildProject = new MSBuildProject(root);
// We can now ask any question about the properties or items in our project file
// and get the correct answer
string spaRootValue = msbuildProject.GetPropertyValue("SpaRoot");
}
private static void MSBuildWorkspaceSetup() {
// Attempt to set the version of MSBuild.
var visualStudioInstances = MSBuildLocator.QueryVisualStudioInstances().ToArray();
var instance = visualStudioInstances.Length == 1
// If there is only one instance of MSBuild on this machine, set that as the one to use.
? visualStudioInstances[0]
// Handle selecting the version of MSBuild you want to use.
: SelectVisualStudioInstance(visualStudioInstances);
Console.WriteLine($"Using MSBuild at '{instance.MSBuildPath}' to load projects.");
// NOTE: Be sure to register an instance with the MSBuildLocator
// before calling MSBuildWorkspace.Create()
// otherwise, MSBuildWorkspace won't MEF compose.
MSBuildLocator.RegisterInstance(instance);
}
private static VisualStudioInstance SelectVisualStudioInstance(VisualStudioInstance[] visualStudioInstances) {
Console.WriteLine("Multiple installs of MSBuild detected please select one:");
for (int i = 0; i < visualStudioInstances.Length; i++) {
Console.WriteLine($"Instance {i + 1}");
Console.WriteLine($" Name: {visualStudioInstances[i].Name}");
Console.WriteLine($" Version: {visualStudioInstances[i].Version}");
Console.WriteLine($" MSBuild Path: {visualStudioInstances[i].MSBuildPath}");
}
while (true) {
var userResponse = Console.ReadLine();
if (int.TryParse(userResponse, out int instanceNumber) &&
instanceNumber > 0 &&
instanceNumber <= visualStudioInstances.Length) {
return visualStudioInstances[instanceNumber - 1];
}
Console.WriteLine("Input not accepted, try again.");
}
}
private class ConsoleProgressReporter : IProgress<ProjectLoadProgress> {
public void Report(ProjectLoadProgress loadProgress) {
var projectDisplay = Path.GetFileName(loadProgress.FilePath);
if (loadProgress.TargetFramework != null) {
projectDisplay += $" ({loadProgress.TargetFramework})";
}
Console.WriteLine($"{loadProgress.Operation,-15} {loadProgress.ElapsedTime,-15:m\\:ss\\.fffffff} {projectDisplay}");
}
}
}
}
Hope fully the title was somewhat descriptive.
I have a winform application written in C# with .net 2.0. I would like to have the last compile date automatically updated to a variable for use in the about box and initial splash box. Currently I have a string variable that I update manually. Is there any way to do this?
VS2008
.net 2.0
c#
Another trick (which you may not be able to use) is to leverage the automatic build and revision numbers generated by .NET. If your AssemblyInfo has:
[assembly: AssemblyVersion("1.0.*")]
The last two numbers are just a date/time stamp. Some code (list below) may help:
Version v = Assembly.GetExecutingAssembly().GetName().Version;
DateTime compileDate = new DateTime((v.Build - 1) * TimeSpan.TicksPerDay + v.Revision * TimeSpan.TicksPerSecond * 2).AddYears(1999);
Edit: here's an alternative answer that may be a little clearer to follow than what I put:
https://stackoverflow.com/a/804895/2258
Pop this into your pre-build events;
echo public static class DateStamp { public readonly static System.DateTime BuildTime = System.DateTime.Parse("%date%"); } > $(ProjectDir)DateStamp.cs
It creates a class called DateStamp like this;
public static class DateStamp
{
public readonly static System.DateTime BuildTime = System.DateTime.Parse("14/12/2009");
}
And you use it like this;
Console.WriteLine("Build on " + DateStamp.BuildTime.ToShortDateString());
This might be a decade too late to be useful, but maybe it'll help someone else.
You can add this target to your csproj file, just paste it in at the end:
<Target Name="BuildDate" BeforeTargets="CoreCompile">
<PropertyGroup>
<SharedAssemblyInfoFile>$(IntermediateOutputPath)CustomAssemblyInfo.cs</SharedAssemblyInfoFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(SharedAssemblyInfoFile)" />
</ItemGroup>
<ItemGroup>
<AssemblyAttributes Include="AssemblyMetadata">
<_Parameter1>AssemblyDate</_Parameter1>
<_Parameter2>$([System.DateTime]::UtcNow.ToString("u"))</_Parameter2>
</AssemblyAttributes>
</ItemGroup>
<WriteCodeFragment Language="C#" OutputFile="$(SharedAssemblyInfoFile)" AssemblyAttributes="#(AssemblyAttributes)" />
</Target>
This will create a CustomAssemblyInfo.cs file in your "obj" folder, that contains a single line: [assembly: AssemblyMetadata("AssemblyDate", "DATE_OF_BUILD_HERE")]
This file will be compiled into your assembly, so you can access it at runtime using reflection, such as with the following code:
using System;
using System.Linq;
using System.Reflection;
public class Application
{
static DateTime BuildDate;
public static DateTime GetBuildDate()
{
if (BuildDate == default(DateTime)) {
var attr = typeof(Application).Assembly.GetCustomAttributes<AssemblyMetadataAttribute>();
var dateStr = attr.Single(a => a.Key == "AssemblyDate")?.Value;
BuildDate = DateTime.Parse(dateStr);
}
return BuildDate;
}
}
One thing to note, the AssemblyMetadataAttribute that I'm using here was only added in .NET 4.5.3, so if you need to support a version previous to that you can simply define your own custom attribute type to hold the date value.
I used this about box from codeproject.com. It was actually written by Jeff Atwood way back in 2004. It figures out the compile time by looking at the date stamp on the assembly file, or calculating it from the assembly version. Perhaps you could extract the relevant code from there.
There is no inbuilt macro or similar for this. Your best bet might be a custom build task, such as on tigris, using the Time task and RegexReplace or similar. Not simple to get right. Personally I use the repository version to update AssemblyInfo - broadly related?
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
...
<SvnInfo LocalPath=".">
<Output TaskParameter="Revision" PropertyName="BuildRev" />
</SvnInfo>
<FileUpdate Files="protobuf-net\Properties\AssemblyInfo.cs"
Regex='(\[\s*assembly:\s*AssemblyVersion\(\s*"[^\.]+\.[^\.]+)\.([^\.]+)(\.)([^\.]+)("\)\s*\])'
ReplacementText='$1.$2.$(BuildRev)$5' />