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.
Related
So I have two dlls, Algorithms.dll and Data_Structures.dll (I made these from projects I found on GitHub). Using the browse feature I have managed to add both of the DLL files as references to my Visual Studio 2017 console project. The problem is I can't do anything else with them. Whenever I try to reference something within either file, it simply cannot be found. The only thing that is recognized is the namespace, but nothing inside of that.
What do I need to do to get VS to find the classes these DLLs contain so I can use them? I am aware I need to use Algorithms.Sorting for the example but I can't call anything so I used this as an example.
P.S. If you need more info, please ask. I'm not sure what's relevant to this issue.
EDIT: Ok, it was misleading to have that kind of example. Corrected but please read the question.
EDIT: I tried this on Monodevelop and get the same issue. Maybe it's not the IDE that's the problem?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Algorithms.Sorting; // Error, Sorting cannot be found, and neither can the file container Sorting
using Data_Structures; //Perfectly ok, can find the namespace
namespace CS_HW2_Testing_App
{
class Program
{
static void Main(string[] args)
{
// I'd like to call MergeSort and so forth here. What am I missing?!
}
}
}
Here's the top piece of the file containing MergeSort if it helps
using System;
using System.Collections.Generic;
using Algorithms.Common;
namespace Algorithms.Sorting
{
public static class MergeSorter
{
//
// Public merge-sort API
public static List<T> MergeSort<T>(this List<T> collection, Comparer<T> comparer = null)
{
comparer = comparer ?? Comparer<T>.Default;
return InternalMergeSort(collection, 0, collection.Count - 1, comparer);
}
...
In the first code block, you're importing the wrong namespace: using Algorithms.MergeSort should be using Algorithms.Sorting. Then you can use MergeSorter.MergeSort<T>(...) in your code!
You need to reference the namespace not the class.
using Algorithms.Sorting; //instead of using Algorithms.MergeSort;
Plus make sure the classes are public
Background
I'm using StackExchange.Precompilation to implement aspect-oriented programming in C#. See my repository on GitHub.
The basic idea is that client code will be able to place custom attributes on members, and the precompiler will perform syntax transformations on any members with those attributes. A simple example is the NonNullAttribute I created. When NonNullAttribute is placed on a parameter p, the precompiler will insert
if (Object.Equals(p, null)) throw new ArgumentNullException(nameof(p));
at the beginning of the method body.
Diagnostics are awesome...
I would like to make it difficult to use these attributes incorrectly. The best way I have found (aside from intuitive design) is to create compile-time Diagnostics for invalid or illogical uses of attributes.
For example, NonNullAttribute does not make sense to use on value-typed members. (Even for nullable value-types, because if you wanted to guarantee they weren't null then a non-nullable type should be used instead.) Creating a Diagnostic is a great way to inform the user of this error, without crashing the build like an exception.
...but how do I test them?
Diagnostics are a great way to highlight errors, but I also want to make sure my diagnostic creating code does not have errors. I would like to be able to set up a unit test that can precompile a code sample like this
public class TestClass {
public void ShouldCreateDiagnostic([NonNull] int n) { }
}
and confirm that the correct diagnostic is created (or in some cases that no diagnostics have been created).
Can anyone familiar with StackExchange.Precompilation give me some guidance on this?
Solution:
The answer given by #m0sa was incredibly helpful. There are a lot of details to the implementation, so here is the unit test actually looks like (using NUnit 3). Note the using static for SyntaxFactory, this removes a lot of clutter in the syntax tree construction.
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NUnit.Framework;
using StackExchange.Precompilation;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace MyPrecompiler.Tests {
[TestFixture]
public class NonNull_CompilationDiagnosticsTest {
[Test]
public void NonNullAttribute_CreatesDiagnosticIfAppliedToValueTypeParameter() {
var context = new BeforeCompileContext {
Compilation = TestCompilation_NonNullOnValueTypeParameter(),
Diagnostics = new List<Diagnostic>()
};
ICompileModule module = new MyPrecompiler.MyModule();
module.BeforeCompile(context);
var diagnostic = context.Diagnostics.SingleOrDefault();
Assert.NotNull(diagnostic);
Assert.AreEqual("MyPrecompiler: Invalid attribute usage",
diagnostic.Descriptor.Title.ToString()); //Must use ToString() because Title is a LocalizeableString
}
//Make sure there are spaces before the member name, parameter names, and parameter types.
private CSharpCompilation TestCompilation_NonNullOnValueTypeParameter() {
return CreateCompilation(
MethodDeclaration(ParseTypeName("void"), Identifier(" TestMethod"))
.AddParameterListParameters(
Parameter(Identifier(" param1"))
.WithType(ParseTypeName(" int"))
.AddAttributeLists(AttributeList()
.AddAttributes(Attribute(ParseName("NonNull"))))));
}
//Make sure to include Using directives
private CSharpCompilation CreateCompilation(params MemberDeclarationSyntax[] members) {
return CSharpCompilation.Create("TestAssembly")
.AddReferences(References)
.AddSyntaxTrees(CSharpSyntaxTree.Create(CompilationUnit()
.AddUsings(UsingDirective(ParseName(" Traction")))
.AddMembers(ClassDeclaration(Identifier(" TestClass"))
.AddMembers(members))));
}
private string runtimePath = #"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\";
private MetadataReference[] References =>
new[] {
MetadataReference.CreateFromFile(runtimePath + "mscorlib.dll"),
MetadataReference.CreateFromFile(runtimePath + "System.dll"),
MetadataReference.CreateFromFile(runtimePath + "System.Core.dll"),
MetadataReference.CreateFromFile(typeof(NonNullAttribute).Assembly.Location)
};
}
}
I figure you want to add you diagnostics before the actual emit / compilation, so the steps would be:
create your CSharpCompilation, make sure it has no diagnostic errors before going further
create an BeforeCompileContext, and populate it with the compilation and an empty List<Diagnostic>
create an instance of your ICompileModule and call ICompileModule.BeforeCompile with the context from step 2
check that it contains the required Diagnostic
I am currently working with Visual Studio Enterprise 2015 Version 14.0.25431.01 Update 3 and have used an MS c# example program to test the built-in unit test and code coverage function. The unit test is working perfectly fine, but everytime I try to start code coverage after the test finished, I get an error message saying:
"Empty results generated: No binaries were instrumented. Make sure the tests ran, required binaries were loaded, had matching symbol files, and were not excluded through custom settings. For more information see: http://go.microsoft.com/fwlink/?LinkID=253731.".
Of course, I checked the link for troubleshooting and worked through all listed issues there, but without any success. I also tried several solutions which should have worked in earlier versions (use command promp to instrument binary or to run code coverage, deleting test results Folder and the VS Solution User Option .suo file, etc.), but still haven't found anything useful working for my case.
Does anybody face the same problem or knows a possible solution for it?
Thank you very much in advance!
Best,
Steve
PS: I am using the standard settings, but with all optimizations turned off. My test project is located in the same solution as my source project I want to test. Here is the code for the example program:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace codecoverage
{
public class Program
{
static void Main(string[] args)
{
Program prog = new Program();
prog.TestFunction(0);
}
public int TestFunction(int input)
{
if (input > 0)
{
return 1;
}
else
{
return 0;
}
}
}
}
The Test Class is defined as followed:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using codecoverage;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace codecoverage.Tests
{
[TestClass()]
public class ProgramTests
{
[TestMethod()]
public void TestFunctionTest2()
{
Program target = new Program();
int input = 1;
int expected = 1;
int actual;
actual = target.TestFunction(input);
Assert.AreEqual(expected, actual, "CodeCoverage.Program.TestFunction did not return the expected value.");
}
}
}
i have been looking for the solution. and found that it's actually a PDB file problem. all you need to do is to go to this linker tab->debugging. and set option "Generate full program database file" to Yes. this is due to the change VS2015 introduced for /Debug:FASTLINK. setting it to 'yes' would override it.
I'm currently doing a project where I have to use the Affectiva SDK to analyse some videos that I have recorded. I have downloaded the files, which they have given me and started writing the code for the SDK to work, however when calling the callback functions in my code, Visual Studio doesn't seem to accept the arguments that are put in. So I figured that the interfaces for the callback functions must be done. I'm not really clear on how to this though, since I thought this was all done in their assembly code. My code so far looks like this:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Affdex;
namespace ConsoleApplication2
{
class Program
{
public interface FaceListener { }
public interface ImageListener { }
public interface ProcessStatusListener { }
static void Main(string[] args)
{
VideoDetector detector = new VideoDetector(15);
String licensePath = "C:/Users/hamud/Desktop/sdk_ahmedmudi1992#gmail.com.license";
detector.setLicensePath(licensePath);
String classifierPath = "C:/Programmer/Affectiva/Affdex SDK/data";
detector.setClassifierPath(classifierPath);
detector.setFaceListener(this);
detector.setImageListener(this);
detector.setProcessStatusListener(this);
detector.setDetectSmile(true);
detector.setDetectSurprise(false);
detector.setDetectBrowRaise(false);
detector.setDetectAnger(false);
detector.setDetectDisgust(false);
detector.setDetectAllExpressions(false);
detector.start();
detector.stop();
}
}
}
As far as I know, I have to write code for the interfaces if I'm not mistaken... Or do I? Please help.
Here is a tutorial on getting started to analyze the video files.
As far as I know, I have to write code for the interfaces if I'm not mistaken... Or do I?
No you don't. You just have to implement the methods in the interfaces if you were to use them.
Here is the link to the sample app that uses Camera Detector which you can relate to since both the Camera Detector and the Video Detector implement the FaceListener, ProcessListener and ImageListener Interfaces.
EDIT: You have to implement the Listeners. For example in the code sample you are using the FaceListener so you need to write the implementation for the callbacks viz onFaceFound() and onFaceLost().
You may also want to create an object of processStatusListener and wait for the process to end for a video file something like this:
AffdexProcessStatusListener processStatusListener = new AffdexProcessStatusListener();
processStatusListener.wait();
Here is a link to our C# app AffdexMe which uses CameraDetector. You may find examples of CameraDetector, PhotoDetector, VideoDetector and FrameDetector in our getting started guide.
I need to resolve target paths from an MSI database, outside of the installation. I am currently doing this using the Wix SDK by querying the database's Directory and File tables and constructing the paths from there, but resolving paths seems like something that should already be built-in. Is there a library that does this, even something unofficial, or am I stuck with doing it on my own?
This question has already been asked for C++, but the only answer somehow misunderstood the question to be about strings.
I don't really mind performance. My real concern is with resolving special folders like ".:Fonts", ".:Windows", ".:WinRoot", etc. - which I can still do in my own code but not very elegantly.
I did the same thing you did when DTF first came out. I wrote all the queries and loops to get the data I was working for. And the performance was kind of painful.
Then I noticed the InstallPackage class in the Microsoft.Deployment.WindowsInstaller.Package assembly. I felt kind of silly when I saw how fast and simple the following code is using that class:
using System;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Deployment.WindowsInstaller.Package;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
using (var package = new InstallPackage("foo.msi", DatabaseOpenMode.ReadOnly))
{
foreach (var filePath in package.Files)
{
Console.WriteLine(filePath.Value);
}
Console.WriteLine("Finished");
Console.Read();
}
}
}
}