Krafs.Publicizer 2.2.1

dotnet add package Krafs.Publicizer --version 2.2.1                
NuGet\Install-Package Krafs.Publicizer -Version 2.2.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Krafs.Publicizer" Version="2.2.1">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Krafs.Publicizer --version 2.2.1                
#r "nuget: Krafs.Publicizer, 2.2.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Krafs.Publicizer as a Cake Addin
#addin nuget:?package=Krafs.Publicizer&version=2.2.1

// Install Krafs.Publicizer as a Cake Tool
#tool nuget:?package=Krafs.Publicizer&version=2.2.1                

Usage

Publicizer needs to be told what private members you want access to. You do this by defining Publicize-items in your project file.

All members in specific assemblies:

<ItemGroup>
    <Publicize Include="AssemblyOne" />
    <Publicize Include="AssemblyTwo;AssemblyThree" />
</ItemGroup>

Specific members:

<ItemGroup>
    <Publicize Include="AssemblyOne:MyNamespace.MyType._myPrivateField" />
    <Publicize Include="AssemblyOne:MyNamespace.MyOtherType._myOtherPrivateField" />
</ItemGroup>

Publicize assemblies from a PackageReference

PackageReferences, like other kinds of References, point towards one or more underlying assemblies. Publicizing these assemblies is just a matter of finding out what the underlying assemblies are called, and then specify them as explained above.

Publicize All

You can use this shorthand property to publicize all assemblies referenced by your project:

<PropertyGroup>
    <PublicizeAll>true</PublicizeAll>
</PropertyGroup>

Save the project file and the changes should take effect shortly. If not, try performing a Restore.

Diagnostics

Publicizer logs to MSBuild. However, for convenience it is also possible to log to a custom log file by setting:

<PropertyGroup>
    <PublicizerLogFilePath>path/to/logfile</PublicizerLogFilePath>
</PropertyGroup>

If the file does not exist it will be created.

The file is overwritten on every execution.

Clean

You can instruct Publicizer to clear its cache everytime the project is cleaned:

<PropertyGroup>
    <PublicizerClearCacheOnClean>true</PublicizerClearCacheOnClean>
</PropertyGroup>

This is mostly useful when troubleshooting Publicizer and you want logs to publicize on every rebuild instead of using the cached assemblies.

How Publicizer works

There are two obstacles with accessing private members - the compiler and the runtime. The compiler won't compile code that attempts to access private members, and even if it would - the runtime would throw a MemberAccessException during execution.

Publicizer addresses the compiler issue by copying the assemblies, rewriting the access modifiers to public, and feeding those edited assemblies to the compiler instead of the real ones. This makes the compilation succeed.

The runtime issue is solved by instructing the runtime to not throw MemberAccessExceptions when accessing private members. This is done differently depending on the runtime. Publicizer implements two strategies: Unsafe and IgnoresAccessChecksTo.

Unsafe means that the assembly will be compiled with the unsafe flag.

IgnoresAccessChecksTo emits an IgnoresAccessChecksToAttribute to your source code, which then becomes part of your assembly.

Unsafe works for most versions of Mono. IgnoresAccessChecksTo should work for most other runtimes, like CoreClr. That said - there could be exceptions.

These strategies can be toggled on or off by editing the PublicizerRuntimeStrategies-property in your project file.

Both strategies are enabled by default:

<PropertyGroup>
    <PublicizerRuntimeStrategies>Unsafe;IgnoresAccessChecksTo</PublicizerRuntimeStrategies>
</PropertyGroup>

However, if you e.g. know that your code runs fine with just the Unsafe strategy, you can avoid including the IgnoresAccessChecksToAttribute by telling Publicizer to only use Unsafe:

<PropertyGroup>
    <PublicizerRuntimeStrategies>Unsafe</PublicizerRuntimeStrategies>
</PropertyGroup>

Quirks

Publicizer works by hacking the compiler and runtime, and there are a couple of quirks to be aware of.

Overriding publicized members

Overriding a publicized member will throw an error at runtime. For example, say the following class exists in a referenced assembly ExampleAssembly:

namespace Example;
public abstract class Person
{
    protected abstract string Name { get; }
}

If you publicize this assembly, then Person.Name will be changed to public. If you then create a subclass Student, it might look like this:

public class Student : Person
{
    public override string Name => "Foobar";
}

This compiles just fine. However, during execution the runtime is presumably loading the original assembly where Person.Name is protected. So you have a Student class with a public Name-property overriding a protected Name-property on the Person class. This will cause an access check mismatch at runtime and throw an error.

You can avoid this by instructing Publicizer to not publicize Person.Name. You can use the DoNotPublicize-item for this:

<ItemGroup>
    <Publicize Include="ExampleAssembly" />
    <DoNotPublicize Include="ExampleAssembly:Example.Person.Name" />
</ItemGroup>

However, if there are a lot of protected members you have to override, doing this for all of them can be cumbersome. For this scenario, you can instruct Publicizer to ignore all virtual members in the assembly:

<ItemGroup>
    <Publicize Include="ExampleAssembly" IncludeVirtualMembers="false" />
</ItemGroup>

Compiler-generated member name conflicts

Sometimes assemblies contain members generated automatically by the compiler, like backing-fields for events. These generated members sometimes have names that conflict with other member names when they become public.

You can solve this in the same ways as above - either by using individual DoNotPublicize-items, or by telling Publicizer to ignore all compiler-generated members in the assembly:

<ItemGroup>
    <Publicize Include="ExampleAssembly" IncludeCompilerGeneratedMembers="false" />
</ItemGroup>

If you opt to ignore all virtual and/or compiler-generated members, you can still publicize specific ignored members by specifying them explicitly:

<ItemGroup>
    <Publicize Include="ExampleAssembly" IncludeCompilerGeneratedMembers="false" IncludeVirtualMembers="false" />
    <Publicize Include="ExampleAssembly:Example.Person.SpecificMember" />
</ItemGroup>

Acknowledgements

This project builds upon rwmt's Publicise, simplyWiri's TaskPubliciser, and this gist by Zetrith.

License

MIT

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Krafs.Publicizer:

Package Downloads
Sl4vP0weR.HotReload

Mono HotReload experience.

GitHub repositories (12)

Showing the top 5 popular GitHub repositories that depend on Krafs.Publicizer:

Repository Stars
skyarkhangel/Hardcore-SK
Rimworld Hardcore SK project, our discord server:
bbradson/Performance-Fish
Performance Mod for RimWorld
rwmt/Multiplayer
Zetrith's Multiplayer mod for RimWorld
CombatExtended-Continued/CombatExtended
Combat Extended mod for RimWorld
KSP-RO/RealismOverhaul
Multipatch to KSP to give things realistic stats and sizes
Version Downloads Last updated
2.2.1 23,556 1/25/2023
2.2.0 617 1/17/2023
2.1.0 1,339 12/1/2022
2.0.1 5,473 8/31/2022
2.0.1-beta 233 8/30/2022
2.0.0 422 8/28/2022
1.0.3 3,736 7/28/2022
1.0.2 3,517 1/24/2022
1.0.1 2,977 9/12/2021
1.0.0 428 8/1/2021