I'm trying to experiment with a specific source generator library and I wanted to try and modify some parts of it. To do that cloned the repository for that library and referenced two .csproj files in it from my own .NET 7 project. This looks at first like it would work, VS Code recognized the imports and doesn't complain, but the actual source generator part does not seem to work.
The specific library I'm using is Mapperly and I referenced it in my .csproj file as following:
<ItemGroup>
<ProjectReference Include="..\mapperly\src\Riok.Mapperly.Abstractions\Riok.Mapperly.Abstractions.csproj" />
<ProjectReference Include="..\mapperly\src\Riok.Mapperly\Riok.Mapperly.csproj" />
</ItemGroup>
The error I get is
Partial method 'MyMapper.MapToReadModel(MyEntity)' must have an implementation part because it has accessibility modifiers.
This code compiles if I add the complete nuget package of this library, and it stops working with this error if I remove the nuget package and reference the projects as I've shown. My understanding of this error would be that it indicated that the source generator either did not generate any source, or the build process doesn't use the generated source for some reason. The way this library works is that you define partial methods for mapping, and the source generator fills in the actual code, so that last step seems to fail in this case.
Obviously the way I'm referencing this library makes it behave differently than what happens when I add the full nuget package. The repository has more .csproj file, but those are all tests or samples. It also has a .sln file, but it seems I can't reference that directly.
I'm also using VS Code while the information I could find on this often assume Visual Studio, not sure if there are any limitations here with VS Code only.
What is the proper way to reference a local library that contains a source generator so that it will work properly in VS Code? Or am I misunderstanding the problem here and the cause is something else entirely?
The library you are referencing is not just regular library but a source generator, you need to reference them in a special way. You can see an example in documentation for source generators. Also note how authors of this library reference their source generator project in their tests:
<!-- For local development, simply reference the projects. -->
<ItemGroup Condition="'$(MapperlyNugetPackageVersion)' == ''">
<ProjectReference Include="..\..\src\Riok.Mapperly\Riok.Mapperly.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\src\Riok.Mapperly.Abstractions\Riok.Mapperly.Abstractions.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="true" />
</ItemGroup>
I have found an interesting case which I can't fully understand.
My runtime - PowerShell 7.2.4
I have two PowerShell Core modules: Net6PowerShellModule and NetstandardPowerShellModule.
Net6PowerShellModule.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<None Remove="Net6PowerShellModule.psd1" />
</ItemGroup>
<ItemGroup>
<Content Include="Net6PowerShellModule.psd1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="System.Management.Automation" Version="7.2.4" />
</ItemGroup>
</Project>
Test-Net6Cmdlet.cs
using System.Management.Automation;
namespace Net6PowerShellModule
{
[Cmdlet("Test", "Net6Cmdlet")]
public class TestNet6Cmdlet : PSCmdlet
{
protected override void ProcessRecord()
{
var type = typeof(Microsoft.Extensions.DependencyInjection.IServiceCollection);
WriteObject("Net6 Cmdlet Run.");
}
}
}
Net6PowerShellModule.psd1
#{
RootModule = 'Net6PowerShellModule.dll'
ModuleVersion = '0.0.1'
GUID = '8c1bd929-32bd-44c3-af6b-d9dd261e34f3'
CmdletsToExport = 'Test-Net6Cmdlet'
}
NetstandardPowerShellModule.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<None Remove="NetstandardPowerShellModule.psd1" />
</ItemGroup>
<ItemGroup>
<Content Include="NetstandardPowerShellModule.psd1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
<PackageReference Include="System.Management.Automation" Version="6.1.2" />
</ItemGroup>
</Project>
Test-NetstandardCmdlet.cs
using System.Management.Automation;
namespace NetstandardPowerShellModule
{
[Cmdlet("Test", "NetstandardCmdlet")]
public class TestNetstandardCmdlet : PSCmdlet
{
protected override void ProcessRecord()
{
var type = typeof(Microsoft.Extensions.DependencyInjection.IServiceCollection);
WriteObject("Netstandard 2.0 Cmdlet Run.");
}
}
}
NetstandardPowerShellModule.psd1
#{
RootModule = 'NetstandardPowerShellModule.dll'
ModuleVersion = '0.0.1'
GUID = 'eef615d0-aecf-4e89-8f4c-53174f8c99d6'
CmdletsToExport = 'Test-NetstandardCmdlet'
}
Next, I have two possible use cases for those modules:
Import Net6PowerShellModule
Import NetstandardPowerShellModule
Run Test-Net6Cmdlet
Run Test-NetstandardCmdlet
Result - everything runs correctly. The only loaded assembly is Microsoft.Extensions.DependencyInjection.Abstractions version 6.0.0.
Import Net6PowerShellModule
Import NetstandardPowerShellModule
Run Test-NetstandardCmdlet
Run Test-Net6Cmdlet.
Result - error: Test-Net6Cmdlet: Could not load file or assembly 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. Could not find or load a specific file. (0x80131621)
Based on this question, this is not an expected behavior:
Subsequent attempts to load a different version of the assembly:
cause a statement-terminating error in Powershell (Core) 7+
Assembly with same name is already loaded.
Why does the first scenario work then?
If I add the RequiredAssemblies = #('Microsoft.Extensions.DependencyInjection.Abstractions.dll') line in both module manifests, I got an expected behavior: regardless of which module is loaded first, I still got an error: Assembly with same name is already loaded.
Update 1
Articles that I've already looked at:
https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/resolving-dependency-conflicts
PowerShell: why am I able to load multiple versions of the same .NET assembly in one session and also "bypass Dependency hell"?
https://devblogs.microsoft.com/powershell/resolving-powershell-module-assembly-dependency-conflicts/ - this article explains a lot about Assembly Load Contexts, but I still cannot understand the difference between having RequiredAssemblies defined in .psd1 files or using the dependencies in code.
tl;dr
When authoring a module, it is best to use a module manifest (.psd1 file) with a RequiredAssemblies key to declare dependent (helper) assemblies, because it makes loading of such assemblies predictable, because it happens at the time of module import.
In Windows PowerShell, attempts to load a different version of an already-loaded assembly are quietly ignored and the newly imported module is made to use the already-loaded assembly too, which may or may not work.
In PowerShell (Core) v6+, attempts to load a different version of an already-loaded assembly are categorically prevented.
If you use implicit loading of dependent assemblies, based on .NET's mechanisms, the timing of when these dependencies are loaded isn't guaranteed, and, in PowerShell Core, you'll bypass the check that prevents attempts to load a different version of an already-loaded assembly - which may or may not work and is only allowed if the later load attempt requests a lower version than the one already loaded.
Non-trivial workarounds are needed to reliably load different versions of a given assembly side by side.
Background information
One of your links, Resolving PowerShell module assembly dependency conflicts, contains all relevant information, but it's a lengthy read with complex, in-depth information, and it doesn't clarify the way in which PowerShell (Core) (v6+) overrides the behavior of the underlying .NET (Core) / .NET 5+ framework.
Terminology: For simplicity, I'll use the following terms:
Windows PowerShell is the legacy, ships-with-Windows Windows-only edition of PowerShell whose latest and final version is 5.1.x.
It is built on top of .NET Framework, the legacy, Windows-only edition of .NET, whose latest and last version is v4.8.x
PowerShell Core (officially now just PowerShell) is the modern, cross-platform, install-on-demand PowerShell edition, whose first (but now obsolete) version was v6 and whose current version as of this writing is v7.2.6.
It is built on top of .NET Core (officially since v5 just .NET), the modern, cross-platform, install-on-demand edition of .NET.
The underlying problem:
At the .NET framework / CLR (Common Language Runtime) level, the fundamental problem is the inability to load different versions of a given assembly side by side into the same application domain (.NET Framework) / ALC (assembly-load context, .NET Core): the version that is loaded first into a session quietly wins, with .NET Core imposing the additional restriction that only attempts to load lower versions succeed (see below).
PowerShell uses only a single application domain / ALC, including for modules, necessitating workarounds to enable side-by-side loading of different versions of a (dependent) assembly. The linked article details various workarounds; a robust, generic, in-process workaround requires PowerShell Core and is limited to binary (compiled) modules and is nontrivial; limited workarounds, some out-of-process, are available for Windows PowerShell and cross-PowerShell-edition code.
Behavior without a workaround:
Windows PowerShell and .NET Framework:
.NET Framework quietly ignores subsequent attempts to load a different version of an already-loaded assembly, and makes the caller use the already-loaded version, irrespective of whether the requested version is higher or lower than the already loaded one.
Curiously, when the caller uses reflection with typeof to inspect the assembly and its types it just loaded (whether implicitly or explicitly), the requested assembly version is reported - even though it is the already loaded assembly and its types that are actually used.
Windows PowerShell does not overlay this behavior with custom functionality, although whether or not you use a module manifest (.psd1 file) with a RequiredAssemblies entry can make a difference with respect to when dependent assemblies are loaded (see below).
Upshot:
Attempts to load a different version of an already-loaded assembly never fail and quietly make the caller use the already-loaded version.
This may or may not work, and you'll only notice at the time the types from the assembly in question are actually used (construction of an instance, property access, method calls, ...).
Notably, it is likely to fail if a later caller attempts to load a higher version of an assembly, as it may rely on features that the already-loaded lower version doesn't have, but even the inverse scenario isn't guaranteed to succeed, given that a higher version may have introduced breaking changes.
PowerShell Core and .NET Core:
.NET Core:
Quietly ignores subsequent attempts to load a lower version of an already-loaded assembly, and makes the caller use the already-loaded version
Throws an exception for attempts to load a higher version: Could not find or load a specific file. (0x80131621)
The rationale appears to be: an already-loaded higher version is at least likely to be backward-compatible with the requested lower version - although, in the absence of semantic versioning for .NET assemblies - this is not guaranteed.
When no exception occurs, and the caller uses reflection with typeof to inspect the assembly and its types it just loaded (whether implicitly or explicitly), it does see the actual, already-loaded assembly version - unlike in .NET Framework.
PowerShell Core does overlay this behavior with custom functionality, unlike Windows PowerShell:
It defines a custom dependency handler for the plugin-oriented [System.Reflection.Assembly]::LoadFrom() .NET Core API, which it uses behind the scenes in the following contexts:
When loading a module with a module manifest (.psd1) that declares dependent assemblies via its RequiredAssemblies entry.
Calls to Add-Type with -Path / -LiteralPath.
in using assembly statements.
This custom dependency resolution handler categorically prevents attempts to load a different version of an already-loaded version, irrespective of which version number is higher.
A statement-terminating Assembly with same name is already loaded error occurs (in the case of using assembly, loading a script breaks in the parsing stage with Cannot load assembly '...').
Upshot:
In PowerShell Core, attempts to load a different version of an already-loaded assembly always fail if you let PowerShell make the load attempt.
Only if you use .NET Core's implicit dependency loading is there a chance that the load attempt succeeds, namely if an attempt is made to load a lower version later. Implicit dependency loading happens automatically, via the [System.Reflection.Assembly]::Load() .NET API, which is based on supplying a dependent assembly's (location-independent) full name and default probing paths for looking for the file hosting it, which notably includes the directory of the calling assembly itself.
This may or may not work, given that versioning of .NET assemblies doesn't use semantic versioning, so a higher version of an assembly isn't guaranteed to be backward-compatible.
Also, the timing of implicit loading of dependencies isn't predictable, and usually doesn't happen until the types from the assembly in question are actually used (construction of an instance, property access, method calls, ...). Thus, if you rely on your modules to use implicit dependency loading, there is no guaranteed relationship between when a module is imported vs. when actual dependency loading is attempted, so it may fail if a dependency with a lower version happens to be loaded first in a given session (but, as stated, even if the higher version is loaded first, you may see a later error when the types are actually used).
For our new project we decided to use .NET 5 for compatibility and we chose to split the project into different libraries to maintain it easily.
The problems come out now that we are trying to put them together.
We would like to have a hierarchy of libraries where the middle level reference the low level ones and then in the main project reference the middle ones and automatically gains the references to the low levels. But this is not working as expected.
We started using direct reference to .dll differentiating by Debug and Release version for debugging as follows:
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<Reference Include="Model.Core">
<HintPath>..\..\Model.Core\Model.Core\bin\Debug\net5.0\Model.Core.dll</HintPath>
</Reference>
<Reference Include="Services.Logging">
<HintPath>..\..\..\Services\Services.Logging\Services.Logging\bin\Debug\net5.0\Services.Logging.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' == 'Release' ">
<Reference Include="Model.Core">
<HintPath>..\..\Model.Core\Model.Core\bin\Release\net5.0\Model.Core.dll</HintPath>
</Reference>
<Reference Include="Services.Logging">
<HintPath>..\..\..\Services\Services.Logging\Services.Logging\bin\Release\net5.0\Services.Logging.dll</HintPath>
</Reference>
</ItemGroup>
The problem with this approach is we don't get indirect references resolved (and them are not included inside the middle library) so when running a program we got a FileNotFoundException about the low level library.
Low-level libraries: Logger (wrapper for Serilog NuGet package), Model.Core
Middle-level library: Model.FileSystem
Running program
This is a call example:
Running program -> Model.FileSystem.ReadConfiguration() -> Logger.Log()
At this point we get the FileNotFoundException.
I know we can solve the problem by manually coping the necessary dlls directly inside the bin folder of running program but we would like to have it done automatically.
An alternative way can be to pack all libraries inside NuGet packages and then refer to them. This way references are automatically resolved but it seems over-complicated for our needs. I'm surely missing something but now this seems the procedure and it's quite painful while debugging:
Create the package with Debug configuration
Move the package to the local or private repository
Compile the project referencing the package
Debug
Once the debug ends, repeat 1, 2 and 3 with Release configuration version.
Imho this way there are too many manually parts needed each time we change something and could lead to problems mixing Debug and Release packages when forgetting about changing the package (can happen while compiling many many times).
I liked the direct reference where you can target Release and Debug versions of the same library according to main configuration but it doesn't work.
Sorry for being so verbose but it's quite complicated to explain this problem and I'm not sure I was clear. I asked this question even here How to reference private library in debug/release mode.
Thanks in advance to anyone tries to help me.
I have chosen to include in each solution the project needed for references. This way there are no missing libraries while running.
Not really what I wanted but it's the easiest way.
I created a C# application (MyAppV1) that requires a third party API. My application needs to work with multiple versions of this API, but only a single version at one time. I have setup my solution to change the reference and using statement for different build configurations and I create multiple executable files that each target a different API version.
Presently I have this situation:
MyAppV1_ThirdPartyV1.exe uses ThirdPartyV1.dll
MyAppV1_ThirdPartyV2.exe uses ThirdPartyV2.dll
MyAppV1_ThirdPartyV2_5.exe uses ThirdPartyV2.dll (they didn't change
the library name for the minor version of their software)
MyAppV1_ThirdPartyV3.exe uses ThirdPartyV3.dll
I would like to be able to maintain a list of the versions, perhaps in an App.config and load the appropriate dll library at runtime. I'm having trouble knowing where to begin with this. Is this an appropriate strategy? I'm not sure how best to handle this situation. Multiple versions of my application the only differ with the referenced library seems very clunky to me.
Much of the information I find is related to supporting multiple frameworks, handling the requirement of two versions of the same library downstream at the same time, or needing to load both at the same time. I can't find information on how to handle my particular situation.
This is possible on project level. You can build different configurations in solution and when you add references as below, it will take the desired DLLs
<Choose>
<When Condition="'$(Configuration)|$(Platform)'=='YourSpecialConfiguration1|x64'"><!-- attention here -->
<ItemGroup>
<Reference Include="your.dllv1.name">
<HintPath>yourDllPath_v1\your.dllv1.dll</HintPath><!-- attention here -->
<Private>true</Private>
</Reference>
<!-- more references here -->
</ItemGroup>
</When>
<When Condition="'$(Configuration)|$(Platform)'=='YourSpecialConfiguration2|x64'"><!-- attention here -->
<ItemGroup>
<Reference Include="your.dllv2.name">
<HintPath>yourDllPath_v2\your.dllv2.dll</HintPath><!-- attention here -->
<Private>true</Private>
</Reference>
<!-- more references here -->
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<Reference Include="your.dllname">
<HintPath>yourRegularPath\your.dllname.dll</HintPath><!-- attention here -->
<Private>true</Private>
</Reference>
<!-- AND more references here -->
</ItemGroup>
</Otherwise>
</Choose>
What you see above - option 1.
Option 2 - Different projects for each version. Downside - if you add a file or reference , you need to add to each project
Option 3 - Add all references but declare different namespace aliases (in reference property window) for each. Then in code do conditional compilation like
ISomething myVar;
#if V1
myVar = new namespace1.ClassX();
#elif V2
myVar = new namespace2.ClassX();
#else
. . . .
#endif
And lastly:
"I would like to be able to maintain a list of the versions, perhaps in an App.config and load the appropriate dll library at runtime."
- you probably don't need non of these. You just need to produce your packages with different versions. Loading at runtime will require more coding work while still supplying all DLLs because you don't know what you going to load next time.
I have a Visual Studio C# solution which consists of some projects. One of the projects needs to reference another project which is not part of the solution.
At the beginning I was referencing dlls:
<ItemGroup>
<Reference Include="ExternalProj1, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Proj1\ExternalProj1.dll</HintPath>
</Reference>
</ItemGroup>
However I must reference projects so that those will generate their dlls. In fact If I reference dlls and they have not been created, I need to build those projects separately.
However when referencing projects:
<ItemGroup>
<ProjectReference Include="..\..\Proj1\ExternalProj1">
<Project>{3341b552-a569-4313-aabc-34452fff60ac}</Project>
<Name>ExternalProj1</Name>
</ProjectReference>
</ItemGroup>
However when building the compiler cannot find those assemblies. The strange thing is that building process is reported as completed successfully but the error window reports one warning:
The referenced component ExternalProj could not be found.
So, what am I doing wrong? Thank you
I see you are using ProjectReference, which is what I'm familiar with in plain (non-NET) C++ projects. The Include attribute needs to name the file, not just the base name; e.g.
<ProjectReference Include="..\..\Proj1\ExternalProj1.vcxproj">
That is, ProjectReference is not Reference. See Common MSBuild Project Items
Also, the metadata that determines whether to link the LIB automatically is determined via the supplied props files if it is not specified for that item. Is a managed project even producing a LIB? So this should (with the filename correct) cause the nominated project to be built also as a dependent, doing something with its products is another issue altogether.
Try building from MSBuild.exe command line, not the IDE, to see the pure behavior before the IDE messes things up or adds more issues to figure out. And, feed it the specific proj file you are wanting, not the "solution" file. The .sln file is a strange beast and not only is it possible to have project references not present in the sln, there is no inherent concept of a sln file at all. Other than a list of projects to show in the IDE, it is a magic file converted into a master proj on the fly that lets you name various targets individually without having to know which proj file (or the path to it) which is handy enough, but mainly there for compatibility with VSBuild according to The Books. So avoid it, at least to simplify things to get the behavior you want during the exploration stage. Then add any complications back in if you still want them :) .