Krafs.Publicizer
2.3.0
dotnet add package Krafs.Publicizer --version 2.3.0
NuGet\Install-Package Krafs.Publicizer -Version 2.3.0
<PackageReference Include="Krafs.Publicizer" Version="2.3.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Krafs.Publicizer --version 2.3.0
#r "nuget: Krafs.Publicizer, 2.3.0"
// Install Krafs.Publicizer as a Cake Addin #addin nuget:?package=Krafs.Publicizer&version=2.3.0 // Install Krafs.Publicizer as a Cake Tool #tool nuget:?package=Krafs.Publicizer&version=2.3.0
Publicizer
Publicizer is an MSBuild plugin that allows direct access to private members in .NET-assemblies.
Usage
Publicizer needs to be told what private members you want access to. You do this by defining Publicize-items in your project file.
<ItemGroup>
<Publicize Include="MyAssemblyFileName" />
<Publicize Include="MyAssemblyFileName:MyNamespace.MyType" />
<Publicize Include="MyAssemblyFileName:MyNamespace.MyType`2" />
<Publicize Include="MyAssemblyFileName:MyNamespace.MyType.myField" />
<Publicize Include="MyAssemblyFileName:MyNamespace.MyType.MyProperty" />
<Publicize Include="MyAssemblyFileName:MyNamespace.MyType.MyMethod" />
<Publicize Include="MyAssemblyFileName:MyNamespace.MyType+MyNestedType.myField" />
<Publicize Include="MyAssemblyFileName:MyNamespace.MyType..ctor" />
</ItemGroup>
Regular expressions
Regular expressions are supported with the MemberPattern
attribute.
<ItemGroup>
<Publicize Include="MyAssemblyFileName" MemberPattern="^MyNamespace\.MyType\..*" />
</ItemGroup>
Notes:
- Assemblies are referenced by their file name, excluding file extension.
So, given an assembly called
MyAssemblyFileName.dll
, you reference it asMyAssemblyFileName
. - There is no way to publicize a specific method overload. Specifying a method will publicize all its overloads.
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 the file names of the underlying assemblies, 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
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
|