You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: contrast reflection vs publicizer for non-public access
NonPublicized now reads the same non-public field (isDedicatedUGCInstalled)
via reflection instead of public-only API, so the two plugins are a live
reflection-vs-.Publicized comparison. README explains why publicizing wins:
compile-checked, readable, and faster than string-keyed reflection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: README.md
+32-23Lines changed: 32 additions & 23 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,41 +1,50 @@
1
1
# RocketModFix.Unturned.Redist.Example
2
2
3
-
Two minimal [RocketMod](https://github.com/RocketModFix/RocketModFix) plugins that show how to consume the RocketModFix NuGet packages in an Unturned server plugin — and, side by side, exactly what the [`RocketModFix.Unturned.Redist`](https://github.com/RocketModFix/RocketModFix.Unturned.Redist)**`.Publicized`** package changes.
3
+
Two minimal [RocketMod](https://github.com/RocketModFix/RocketModFix) plugins that read the **same non-public Unturned field two ways** — via reflection (the plain redist) and via the [`RocketModFix.Unturned.Redist`](https://github.com/RocketModFix/RocketModFix.Unturned.Redist)**`.Publicized`** package (a plain field access) — so you can see *why* publicizing is worth it.
4
4
5
5
> These are example **consumer** plugins — their namespaces (`UnturnedRedistExample.*`) are what *you'd* write in your own plugin; they just **reference** the RocketModFix packages.
Both reference the **same game build** (`3.26.3.3`) and both read `SDG.Unturned.Provider.isDedicatedUGCInstalled` — a field that is **non-public** in the stock `Assembly-CSharp.dll`. The only difference is *how*:
13
10
14
-
Both reference the **same game build** (`3.26.3.3`) and are otherwise identical RocketMod plugins. The redist package is the only meaningful difference — that's the whole lesson.
11
+
| Project | Redist package | How it reads the non-public field |
12
+
| --- | --- | --- |
13
+
|[`NonPublicized/`](NonPublicized)|`RocketModFix.Unturned.Redist.Server`|**Reflection** — string-keyed, not compile-checked, slower |
14
+
|[`Publicized/`](Publicized)|`RocketModFix.Unturned.Redist.Server.Publicized` (+ `AllowUnsafeBlocks`) |**Direct field access** — compile-checked, fast |
15
15
16
-
### `NonPublicized` — public API only
16
+
##Why publicize instead of reflection?
17
17
18
-
Reads a **public**member and logs it. The non-public member is shown commented out, because it does **not** compile against the plain redist:
18
+
A direct `Provider.isDedicatedUGCInstalled`**doesn't compile**against the plain redist — the package simply doesn't expose non-public members:
-**Not compile-checked.** The member name is a string — a rename or typo compiles fine and throws at **runtime** (`NullReferenceException` on the missing `field`), on your live server.
34
+
-**Harder to read.** No IntelliSense, casts everywhere, three lines for one read.
35
+
-**Slower.** Every access pays a reflection lookup + boxing; even caching the `FieldInfo` stays indirect.
30
36
31
-
Reads `SDG.Unturned.Provider.isDedicatedUGCInstalled` — **non-public** in the stock `Assembly-CSharp.dll`:
37
+
The **`.Publicized`** package ([`Publicized/`](Publicized/PublicizedExamplePlugin.cs)) rewrites that member to public, so the same read is one normal line:
32
38
33
39
```csharp
34
-
varugcInstalled=Provider.isDedicatedUGCInstalled; // compiles via the .Publicized package
This compiles only because the `.Publicized` package rewrites that member to public, and runs only because the `.csproj` sets `<AllowUnsafeBlocks>true</AllowUnsafeBlocks>` (which lets Unturned's Mono runtime skip the access check).
43
+
-**Compile-checked** — a rename breaks the *build*, not your server.
44
+
-**Reads like ordinary code** — full IntelliSense, no casts.
45
+
-**Plain field-access speed.**
46
+
47
+
The one requirement: set `<AllowUnsafeBlocks>true</AllowUnsafeBlocks>` in the `.csproj`, which lets Unturned's Mono runtime skip the access check on the originally-private member.
39
48
40
49
## Packages used (both plugins)
41
50
@@ -59,14 +68,14 @@ Builds both projects (via `UnturnedRedistExample.sln`); the DLLs land in each pr
59
68
60
69
1. Install [RocketModFix](https://github.com/RocketModFix/RocketModFix#installation) on an Unturned dedicated server (copy the `Rocket.Unturned` folder into `Modules/`) and start it once so it generates `Servers/<ServerID>/Rocket/`.
61
70
2. Copy either built DLL into `Servers/<ServerID>/Rocket/Plugins/` (DLLs go directly in `Plugins/`, not a subfolder).
62
-
3. Start the server and watch the console:
71
+
3. Start the server and watch the console — both read the same value, the hard way and the easy way:
[UnturnedRedistExample.Publicized] Loaded. Read NON-public Provider.isDedicatedUGCInstalled = False via the .Publicized redist.
74
+
[UnturnedRedistExample.NonPublicized] Loaded. Read non-public isDedicatedUGCInstalled = False via REFLECTION ...
75
+
[UnturnedRedistExample.Publicized] Loaded. Read NON-public Provider.isDedicatedUGCInstalled = False via the .Publicized redist.
67
76
```
68
77
69
-
The `Publicized` line is the key one: it proves the non-public member was read **at runtime** (so `AllowUnsafeBlocks` did its job). If publicization were broken you'd get a `FieldAccessException`("is inaccessible") instead.
78
+
Seeing those lines confirms each plugin **loaded** and read the non-public member **at runtime**. (For the publicized one, that also proves `AllowUnsafeBlocks` did its job — if publicization were broken you'd get a `FieldAccessException` instead.)
0 commit comments