Roslyn compilation - how to reference a .NET Standard 2.0 class library - c#

I created a console application project (targeting .NET Core 3.0) and a class library (targeting .NET Standard 2.0). The console application tries to use the Roslyn compiler to compile some C# code that references that previously created class library. I'm hitting some major issues though.
Here's the code for the console application (note that most of it is example code from https://github.com/joelmartinez/dotnet-core-roslyn-sample/blob/master/Program.cs):
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; //nuget Microsoft.CodeAnalysis.CSharp
using Microsoft.CodeAnalysis.Emit;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
//This is a class library I made in a separate project in the solution and added as a reference to the console application project.
//The important bit for the reproduction of the issue is that ClassLibrary1 targets .NET Standard 2.0.
using ClassLibary1;
namespace RoslynIssueRepro
{
class Program
{
static void Main(string[] args)
{
string codeToCompile =
#"
using ClassLibary1;
using System;
namespace RoslynCompileSample
{
public class Writer
{
public void Execute()
{
//1. this next line of code introduces the issue during Roslyn compilation (comment it out and everything runs fine).
//It causes the code to reference a .NET Standard 2.0 class library (and this console app targets .NET Core 3.0).
//Note: If the referenced class library targets .NET Core 3.0, everything works fine.
//The error looks like this:
// CS0012: The type 'Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.
Console.WriteLine(Class1.DoStuff());
Console.WriteLine(""Freshly compiled code execution done!"");
}
}
}";
var refPaths = new[] {
typeof(System.Object).GetTypeInfo().Assembly.Location,
typeof(Console).GetTypeInfo().Assembly.Location,
Path.Combine(Path.GetDirectoryName(typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.Location), "System.Runtime.dll"),
typeof(Class1).GetTypeInfo().Assembly.Location,
//2. So adding a reference to netstandard.dll to alleviate the issue does not work.
//Instead it causes even more compilation errors of this form:
// CS0518: Predefined type 'System.Object' is not defined or imported
// CS0433: The type 'Console' exists in both 'System.Console, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' and 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
//Go ahead and try it by uncommenting the line below:
//Environment.ExpandEnvironmentVariables(#"C:\Users\%USERNAME%\.nuget\packages\netstandard.library\2.0.0\build\netstandard2.0\ref\netstandard.dll")
};
RoslynCompileAndExecute(codeToCompile, refPaths);
}
#region example code from https://github.com/joelmartinez/dotnet-core-roslyn-sample/blob/master/Program.cs
private static void RoslynCompileAndExecute(string codeToCompile, string[] refPaths)
{
Write("Let's compile!");
Write("Parsing the code into the SyntaxTree");
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(codeToCompile);
string assemblyName = Path.GetRandomFileName();
MetadataReference[] references = refPaths.Select(r => MetadataReference.CreateFromFile(r)).ToArray();
Write("Adding the following references");
foreach (var r in refPaths)
Write(r);
Write("Compiling ...");
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
Write("Compilation failed!");
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
foreach (Diagnostic diagnostic in failures)
{
Console.Error.WriteLine("\t{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
}
else
{
Write("Compilation successful! Now instantiating and executing the code ...");
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
var type = assembly.GetType("RoslynCompileSample.Writer");
var instance = assembly.CreateInstance("RoslynCompileSample.Writer");
var meth = type.GetMember("Execute").First() as MethodInfo;
meth.Invoke(instance, null);
}
}
}
static Action<string> Write = Console.WriteLine;
#endregion
}
}
and the code for the ClassLibrary1 is just this:
namespace ClassLibary1
{
public static class Class1
{
public static string DoStuff()
{
return "asdfjkl";
}
}
}
I've commented two places in code with //1 and //2. The first one is the line that introduces the first issue, and causes the compilation to fail. The second spot (currently commented out) is an attempt to work around the first issue by adding a reference to the netstandard.dll file (sorry if the path isn't portable, just where I happened to find it on my machine), but it does not fix anything, only introducing more cryptic errors.
Any idea on the approach I should take to get this code to work?

The first error occur because your referenced library targets netstandard and the console app compilation which references this library must reference netstandard.dll to correctly resolve all corresponding types. So you should add reference to nestandard.dll but it's not all and here you get the second error.
When you try to reference netsandard directly or by transitive you must provide the nestandard.dll corresponding by the target platform. And this netstandard will have a huge of forwarding types to the types on the current target platform. If you look at the #"C:\Users\%USERNAME%\.nuget\packages\netstandard.library\2.0.0\build\netstandard2.0\ref\netstandard.dll" you will find that this netstandard.dll doesn't contain forwards but contains all types directly and of course it contains System.Console. ( I think, it contains all types directly because it is from nuget package which doesn't depend on any target platform, but really not sure for that). And when you try to add it and System.Console.dll by typeof(Console).GetTypeInfo().Assembly.Location you actually get two System.Console in a compilation.
So to resolve this ambiguous you can add netstandard not from this nuget package, but from you current target platform which has all needed forwards. For the .netcore30, for example, you can use netstandard from path_to_dotnet_sdks\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\ (be careful this assemblies and assemblies from nuget package above just for referencing, they doesn't contain real logic). Also you may try to remove reference on System.Console.dll and keep reference on #"C:\Users\%USERNAME%\.nuget\packages\netstandard.library\2.0.0\build\netstandard2.0\ref\netstandard.dll"

Since you're using typeof(X).Assembly to locate all other referenced assemblies, which will implicitly return the assemblies loaded into your App Domain. I would recommend locating the matching netstandard in the same way. However, since netstandard doesn't directly define any types, the best method I've found is to search for it by name;
AppDomain.CurrentDomain.GetAssemblies().Single(a => a.GetName().Name == "netstandard")

var dd = typeof(Enumerable).GetTypeInfo().Assembly.Location;
var coreDir = Directory.GetParent(dd);
MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "netstandard.dll")

Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(), "netstandard.dll")

Related

Runtime error with a very basic reflection example in C#

I'm trying to learn a bit more about System.Reflection using the official microsoft docs. Specifically I'm trying to run the following example:
// Loads an assembly using its file name.
Assembly a = Assembly.LoadFrom("MyExe.exe");
// Gets the type names from the assembly.
Type[] types2 = a.GetTypes();
foreach (Type t in types2)
{
Console.WriteLine(t.FullName);
}
So I made a new console app using dotnet new console -o=customconsole. I then removed ImplicitUsings from my project file (because I don't like that), and came up with the following code:
using System;
using System.Reflection;
namespace get_type_from_assembly
{
internal class Program
{
static void Main(string[] args)
{
// load assembly using full file name
Assembly a = Assembly.LoadFrom("C:\\Users\\bobmarley\\desktop\\temp\\csharp-reflection\\get-type-from-assembly\\bin\\Debug\\net6.0\\console-custom.exe");
// get type names from assembly
Type[] types2 = a.GetTypes();
foreach (Type t in types2)
{
Console.WriteLine(t.FullName);
}
}
}
}
Then I tried to run the generated executable using dotnet run --project=customconsole. I got the following runtime error:
Unhandled exception. System.BadImageFormatException: Bad IL format. The format of the file 'C:\Users\bobmarley\desktop\temp\csharp-reflection\get-type-from-assembly\bin\Debug\net6.0\console-custom.exe' is invalid.
at System.Runtime.Loader.AssemblyLoadContext.LoadFromPath(IntPtr ptrNativeAssemblyLoadContext, String ilPath, String niPath, ObjectHandleOnStack retAssembly)
at System.Runtime.Loader.AssemblyLoadContext.LoadFromAssemblyPath(String assemblyPath)
at System.Reflection.Assembly.LoadFrom(String assemblyFile)
at get_type_from_assembly.Program.Main(String[] args) in C:\Users\bobmarley\desktop\temp\csharp-reflection\get-type-from-assembly\Program.cs:line 11
make: *** [Makefile:5: run] Error 1
I'm not sure why this occurs, because I checked and the executable does exist in the specified path. What is happening here and how can I fix it?
A likely reason is that the your project and the loaded assembly target different platforms, i.e. x86 vs x64, In my experience that is a common reason for BadImageFormatException. Another possible reasons is that one targets .net core while the other targets .net framework.
Dynamically loading an assembly will require that it is compatible with your project. If you want to read arbitrary assemblies you probably want some tool that can extract whatever information you are after from the CIL code directly, without actually loading it.

Retrieve <Version> tag written in csproj file from dll [duplicate]

I am trying to get the executing assembly version in C# 3.0 using the following code:
var assemblyFullName = Assembly.GetExecutingAssembly().FullName;
var version = assemblyFullName .Split(',')[1].Split('=')[1];
Is there another proper way of doing so?
Two options... regardless of application type you can always invoke:
Assembly.GetExecutingAssembly().GetName().Version
If a Windows Forms application, you can always access via application if looking specifically for product version.
Application.ProductVersion
Using GetExecutingAssembly for an assembly reference is not always an option. As such, I personally find it useful to create a static helper class in projects where I may need to reference the underlying assembly or assembly version:
// A sample assembly reference class that would exist in the `Core` project.
public static class CoreAssembly
{
public static readonly Assembly Reference = typeof(CoreAssembly).Assembly;
public static readonly Version Version = Reference.GetName().Version;
}
Then I can cleanly reference CoreAssembly.Version in my code as required.
In MSDN, Assembly.GetExecutingAssembly Method, is remark about method "getexecutingassembly", that for performance reasons, you should call this method only when you do not know at design time what assembly is currently executing.
The recommended way to retrieve an Assembly object that represents the current assembly is to use the Type.Assembly property of a type found in the assembly.
The following example illustrates:
using System;
using System.Reflection;
public class Example
{
public static void Main()
{
Console.WriteLine("The version of the currently executing assembly is: {0}",
typeof(Example).Assembly.GetName().Version);
}
}
/* This example produces output similar to the following:
The version of the currently executing assembly is: 1.1.0.0
Of course this is very similar to the answer with helper class "public static class CoreAssembly", but, if you know at least one type of executing assembly, it isn't mandatory to create a helper class, and it saves your time.
using System.Reflection;
{
string version = Assembly.GetEntryAssembly().GetName().Version.ToString();
}
Remarks from MSDN http://msdn.microsoft.com/en-us/library/system.reflection.assembly.getentryassembly%28v=vs.110%29.aspx:
The GetEntryAssembly method can return null when a managed assembly has been loaded from an unmanaged application. For example, if an unmanaged application creates an instance of a COM component written in C#, a call to the GetEntryAssembly method from the C# component returns null, because the entry point for the process was unmanaged code rather than a managed assembly.
Product Version may be preferred if you're using versioning via GitVersion or other versioning software.
To get this from within your class library you can call System.Diagnostics.FileVersionInfo.ProductVersion:
using System.Diagnostics;
using System.Reflection;
//...
var assemblyLocation = Assembly.GetExecutingAssembly().Location;
var productVersion = FileVersionInfo.GetVersionInfo(assemblyLocation).ProductVersion
This should do:
Assembly assem = Assembly.GetExecutingAssembly();
AssemblyName aName = assem.GetName();
return aName.Version.ToString();
I finally settled on typeof(MyClass).GetTypeInfo().Assembly.GetName().Version for a netstandard1.6 app. All of the other proposed answers presented a partial solution. This is the only thing that got me exactly what I needed.
Sourced from a combination of places:
https://msdn.microsoft.com/en-us/library/x4cw969y(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/2exyydhb(v=vs.110).aspx

SQLite3 Connection Causes Confusing Compile Error

I am attempting to compile my Xamarin project and I am getting a compiler error that I dont understand. What does the following error mean and how can i fix this?
Data\SQLiteClient.cs(4,4): Error CS0012: The type 'System.Threading.Tasks.TaskScheduler' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Threading.Tasks, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. (CS0012) (DtoToVm.Droid)
Relevant information:
Xamarin Forms blank project PCL
Project set to .NET 4.5 or later
Profile: PCL 4.5 - Profile78
Alot of references are not happy but I haven't manually setup or editted these. Maybe a nuget package conflict?
using Xamarin.Forms;
using DtoToVm.Droid.Data;
[assembly: Dependency (typeof(SQLiteClient))]
namespace DtoToVm.Droid.Data
{
using System;
using DtoToVm.Data;
using SQLite.Net.Async;
using System.IO;
using SQLite.Net.Platform.XamarinAndroid;
using SQLite.Net;
public class SQLiteClient : ISQLite
{
public SQLiteAsyncConnection GetConnection ()
{
var sqliteFilename = "Conferences.db3";
var documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
var path = Path.Combine (documentsPath, sqliteFilename);
var platform = new SQLitePlatformAndroid ();
var connectionWithLock = new SQLiteConnectionWithLock (
platform,
new SQLiteConnectionString (path, true));
// The below line causes the compile error
var connection = new SQLiteAsyncConnection (() => connectionWithLock);
return connection;
}
}
}
Edit:
According to this thread I shd change the profile to 7. I've done this and it got rid of all errors but produced a new one:
DtoToVm\packages\Microsoft.Net.Http.2.2.29\lib\portable-net40+sl4+win8+wp71+wpa81\System.Net.Http.dll: Error CS1703: An assembly with the same identity 'System.Net.Http, Version=1.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' has already been imported. Try removing one of the duplicate references. (CS1703) (DtoToVm)
What file could this duplicate reference be in?
I don't really know if System.Threading.Tasks namespace exists in Xamarin or not, If it's exists then reference it, If not then use the regular SqliteConnection and don't use Async one.

Test is trying to load a type from its own assembly

I've got a test class that looks like:
// disclaimer: some type names have been changed to protect IP,
// there may be inconsistencies
using Moq;
using MyComp.MyProj.DataAccessLayer;
namespace Test.Common.Data.DataAccessLayer
{
public class Test
{
Mock<IApplicationData> appData;
Mock<IConfig> config;
public Test()
{
this.appData = new Mock<IApplicationData>();
this.config = new Mock<IConfig>();
}
[Fact]
public void GetNewInstance_WithoutUser( )
{
this.config.Setup(c => c.GetConfigInt(It.IsAny<string>())).Returns(1);
// DalFactory is a type in MyComp.MyProj.DataAccessLayer
var dal = DalFactory.GetDataAccessLayer(1, "fakestring", (IApplicationData)this.appData.Object, (IConfig)this.config.Object);
Assert.IsType <IDataAccessLayer>(dal);
}
}
}
```
The problem here is that whenever it tries to access the DalFactory type, it throws this exception:
System.TypeLoadException : Could not load type 'MyComp.MyProj.DataAccessLayer.DalFactory' from assembly 'DataAccessLayer, Version=0.9.0.0, Culture=neutral, PublicKeyToken=null'.
The Version is the key there, since the MyComp.MyProj.DataAccessLayer is in an assembly with Version 8.0.x while the test is in an assembly with Version 0.9.0 (or 1.0.0 or 0.0.1, I've tried several values).
Question is, why would Moq be trying to load the wrong assembly for this type?
I've tried: looking in the GAC to see if there's an assembly being loaded from there, re-adding the project references, changing AssemblyTitle in the test AssemblyInfo.cs, changing the name of the class the test is in and using an alias on the using statement. No effect.
The method for GetDataAccessLayer is a public static so I don't think InternalsVisibleTo factors in here. If I F12 navigate to the type being tested, it goes fine, gets to the right place.
If I put Assert.True(1 == 1); as the only thing in the Test method, it runs fine and passes.
What should I try next to fix this issue?
Try:
re-build your code in MyComp.MyProj.DataAccessLayer project
re-build Test project
run tests again
see if that helps
Also make sure the client Target Framework is the same for both projects (Properties > Application > Target Framework)

IronPython Excel-Dna Add-In - Exception regarding Microsoft.Dynamic reference

I am getting started with developing an Excel-DNA addin using IronPython with some C# as a wrapper for the calls to IronPython. With the generous help of the Excel-DNA developer, I have worked through some of the initial kinks of getting a sample up and running, but now I am trying to debug the addin in SharpDevelop, and I'm running into some problems. As I'm completely new to most of this, I'm not really sure if it is an issue with SharpDevelop, .NET, Excel-DNA or IronPython.
I have created two projects in one solution, one is a C# class library. The other is a python class library. I setup the project to debug following a tutorial I found on a blog. I am able to step through the first few lines of C# code, so that is progress, but when I get to the following line:
pyEngine.Runtime.LoadAssembly(myclass);
I get an exception:
"Could not load file or assembly
'Microsoft.Dynamic, Version=1.0.0.0,
Culture=neutral,
PublicKeyToken=31bf3856ad364e35' or
one of its dependencies. The located
assembly's manifest definition does
not match the assembly reference.
(Exception from HRESULT: 0x80131040)"
But I'm pretty sure I have added the Microsoft.Dynamic reference to my project. It is version 1.1.0.20. This is included in the IronPython distribution but also in another location on my computer. I have tried setting the reference to both, but they both have the same version number and appear to be the same file size. Neither one works. Do I need version 1.0.0.0 or am I doing something else wrong? I don't really understand why anything pyEngine (the ScriptEngine returned by Python.CreateEngine()) would try to load a different version than the one included with the distribution.
Code is below. Let me know if you need any other information.
MyAddin.cs
/*
Added these references all as Local Copies - probably not necessary?
System.Windows.Forms
Microsoft.CSharp
ExcelDna.Integration (from Excel-DNA distribution folder)
IronPython (from IronPython folder)
IronPython.Modules (from IronPython folder)
Microsoft.Dynamic (from IronPython folder)
Microsoft.Scripting (from IronPython folder)
Microsoft.Scripting.Metadata (from IronPython folder)
mscorlib (I don't really know why I added this, but it was one of the references in my IronPython class library)
MyClass (this is the reference to my IronPython class - I checked to see that it gets copied in every time I rebuild the solution and it does)
These were automatically added by SharpDevelop when I created the project.
System
System.Core
System.Windows.Forms
System.Xml
System.Xml.Linq
*/
using System;
using System.IO;
using System.Windows.Forms;
using ExcelDna.Integration;
using System.Reflection;
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
public class MyAddIn : IExcelAddIn
{
public void AutoOpen()
{
try
{
string xllDirectory = Path.GetDirectoryName(#"C:/Users/myname/Documents/SharpDevelop Projects/IronPythonExcelDNATest/MyAddIn/bin/Debug/");
string dllPath = Path.Combine(xllDirectory,"MyClass.dll");
Assembly myclass = Assembly.LoadFile(dllPath);
ScriptEngine pyEngine = Python.CreateEngine();
pyEngine.Runtime.LoadAssembly(myclass);
ScriptScope pyScope = pyEngine.Runtime.ImportModule("MyClass");
object myClass = pyEngine.Operations.Invoke(pyScope.GetVariable("MyClass"));
IronTest.AddSomeStuff = pyEngine.Operations.GetMember<Func<double, double,double>>(myClass, "AddSomeStuff");
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
public void AutoClose()
{
}
}
public class IronTest
{
public static Func<double, double, double> AddSomeStuff;
public static double TestIPAdd(double val1, double val2)
{
try
{
return AddSomeStuff(val1, val2);
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
return double.NaN;
}
}
}
MyClass.py
class MyClass:
def __init__(self):
pass
def AddSomeStuff(self,x,y):
return x + y
Your IronPython stuff probably needs to run under the .NET 4 runtime. To tell Excel-DNA to load .NET 4, you have to explicitly add a RuntimeVersion attribute in the main .dna file. Your .dna file will start with something like:
<DnaLibrary RuntimeVersion="v4.0"> ... </DnaLibrary>
The default behaviour, if the attribute is omitted, is to load the .NET 2.0 version of the runtime (which is also used by .NET 3.0 and 3.5).
It might be possible to host IronPython under the .NET 2.0 runtime, but then you need to deal with the DLR libraries yourself, whereas they are built-in and already installed with .NET 4.

Categories

Resources