Library / SDK
castleproject/Core avatar
castleproject/Core

castleproject/Core: a C# proxy generator still carrying a .NET Framework tier

Castle Core, including Castle DynamicProxy, Logging Services and DictionaryAdapter

2,314 stars483 forksC#NOASSERTION

At a glance

What is it?
Castle Core is a long lived C# library holding Castle DynamicProxy, a logging abstraction and DictionaryAdapter, and the clearest statement of its audience is a table of conditional compilation symbols: application domains, assembly saving, serialization and configuration management exist only on .NET Framework 4.6.2, while by-ref-like types exist only on .NET 8 and later.
Who is it for?
Castle Core earns a place in a Windows enterprise codebase that still targets .NET Framework and wants a proxy generator that works on every tier it must support, which is a rarer thing in 2026 than it sounds.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 170 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The README is a build document, which is more informative than usual

Castle Core's README is short, and almost none of it describes the API. There is a one paragraph summary naming three things: common Castle Project abstractions including logging services, Castle DynamicProxy described as a lightweight runtime proxy generator, and Castle DictionaryAdapter. Then the document goes straight to releases, licensing, contributing and building. The API documentation is linked out to a separate document in the docs directory.

That shape is worth noting because it tells you how the project expects to be consumed. A library whose README is a build and release guide is a library whose users arrive already knowing what it does, usually because a framework above them depends on it. That is a common and unremarkable situation for infrastructure code, and it explains a great deal about the rest of the repository.

The three components do not obviously belong together, and the documentation offers no guidance on when to reach for which. DynamicProxy is the famous one, the part most likely to be used indirectly through a container or an interceptor library. Logging services and DictionaryAdapter are the parts a direct user is most likely to touch, and both are the kind of small abstraction that gets adopted once and then lives in a codebase for a decade. That is the profile of a dependency rather than a product, and it is the right lens for everything that follows.

The project identifies itself as community driven, with contribution handled through a separate Home repository rather than in this one, and it dates itself from 2004 with a copyright line running to 2026.

Two release pairs, then a silence that needs explaining

The release history is short enough to read at a glance and irregular enough to be worth laying out. Version 5.1.0 shipped on 2022-08-02. Version 5.1.1 followed on 2022-12-30, four months later. Then nothing for twenty-seven months, until 5.2.1 on 2025-03-10. The last push to the default branch, which is named master rather than main, is dated 2026-04-14, and the repository is not archived.

So the pattern is not decay, it is a mature library with an irregular cadence. Two patch releases close together, a long gap, one feature release, and then eighteen months of commits after it without a tag. For infrastructure code that pattern is normal, and in some ways healthy: a library that has existed for two decades and ships when a real change is ready is not the same as a library that is struggling to keep up with a dependency it cannot control.

It is still a fact to plan around. If your team pins a version, the most recent one available is 5.2.1 from March 2025, which means you are pinning something roughly a year and a half old and should confirm for yourself that it targets the runtime you intend to run on. If your team tracks the default branch instead, you are consuming eighteen months of unreleased changes, which for a library that compiles differently per target framework is a larger risk than it would be for most code.

The one release note in the visible history is a patch on a minor version, and the debug symbol story goes back further: separate symbol packages have been published from the build artifacts since version 4.1.0, with a linked example of the artifact listing for that release.

Five conditional symbols tell you who the library is for

The most technically informative thing in the repository is a table of conditional compilation symbols, and reading it closely describes both the library's architecture and its audience better than any feature list would.

Five symbols are defined per build configuration. `FEATURE_APPDOMAIN` enables support for features that use an application domain in the host. `FEATURE_ASSEMBLYBUILDER_SAVE` enables saving the dynamically generated proxy assembly. `FEATURE_BYREFLIKE` enables by-ref-like types such as the span types. `FEATURE_SERIALIZATION` enables serialization of dynamic proxies and other types. `FEATURE_SYSTEM_CONFIGURATION` enables features that use the system configuration facility and its manager.

The distribution across targets is what matters. AppDomain, assembly saving, serialization and configuration management are enabled on .NET Framework 4.6.2 and disabled on .NET Standard 2.0, .NET 8 and .NET 9. The by-ref-like symbol is the exact inverse: disabled on .NET Framework 4.6.2 and .NET Standard 2.0, enabled on .NET 8 and .NET 9.

Two conclusions follow. First, this is a library that still takes a legacy target seriously enough to maintain a distinct code path for it, and that path is not a stub. Serializing a dynamic proxy is a real feature that took years to get right, and so is saving the generated assembly. Any library supporting modern .NET only would have deleted all of that. Second, the split is not arbitrary, it follows what the platform can actually do. Application domains and the configuration manager do not exist in modern .NET, and the span types cannot be boxed or reflected over the way the older targets require.

There is one loose thread here. The table's last column is .NET 9, while the SDK required to build the project is .NET 10 and the runtimes required to test it include .NET 10. A table that stops one version short of the toolchain it documents is a small sign that this part of the documentation is maintained separately from the build configuration, which is worth keeping in mind before you rely on it as a complete map.

Running the tests means installing four runtimes

Compiling requires only the .NET 10 SDK. Running the unit tests requires considerably more, and the requirement is spelled out with a workaround.

To run the full suite you need .NET Framework 4.6.2 or later plus the .NET 8, .NET 9 and .NET 10 runtimes, all installed side by side. That is the direct consequence of the target table above: a test that verifies behaviour on .NET Framework cannot be skipped just because the machine cannot run it, or the library's most distinctive code path stops being tested at all. If you do not have all of them, the documented alternative is to run the tests for one target framework at a time using a framework flag on the test command.

That fallback is the interesting part, because it changes what a green build means. On a machine with only the modern runtimes, a passing suite tells you the modern targets are fine and says nothing about the framework target. Any team taking a red build seriously needs to decide which of those two claims they are relying on before they merge.

There is also a runtime that has been deliberately removed from the matrix. Tests used to run on the Mono 6.0 runtime, and the project states that it stopped doing so because Mono itself has been deprecated, with a link to the official announcement. That is a small but real data point about the direction of travel: one of the oldest cross-platform .NET runtimes left the test matrix not because it failed but because the project around it ended, and the project's response was to drop it rather than carry it.

AppVeyor carries the build, the preview feed and the symbols

Continuous integration runs on AppVeyor, configured from a file at the repository root, and the same service is doing three jobs that other projects usually spread across different tools.

The first is the build itself, launched by one of two scripts depending on the platform:

console
build.cmd
./build.sh

There is a preview NuGet feed hosted on AppVeyor, and the documentation lists it as the feed for Windows and Linux. A preview feed hosted by the CI system rather than by a package registry is an older pattern, and it has one property worth naming: builds that are not yet released are distributed from the same place that produced them, so there is no separate infrastructure to keep alive.

The second is symbol packages. Since version 4.1.0, debugging symbols are published as separate packages in the build artifacts, with a direct link to the artifact listing for that release. This matters more than it sounds for a library that generates code at runtime. A stack trace through a dynamic proxy is only useful if the line numbers resolve, and separate symbol packages are what make that work on every platform rather than only on the build server.

The third is the platform story. The build scripts are two files, a command script for Windows and a shell script for Linux, so the same repository is built natively on both rather than through a container or a cross compilation step. For a library whose oldest target is .NET Framework, building on Windows is not a convenience, it is a requirement, since the framework target cannot be produced on Linux at all.

Together these three functions explain something about the project's continuity. A two decade old library running on a CI service that predates most of its competitors is not a sign of neglect. It is a sign of a build pipeline that was set up once, works, and has never needed the attention that a migration would demand.

Two build scripts, an XML solution file and a ReSharper settings file

The repository root is a small inventory of build decisions, and several entries describe a project that is being maintained rather than merely kept alive.

There are two entry scripts, `build.cmd` for Windows and `build.sh` for Linux, matching the platform split in the CI configuration. There is a buildscripts directory and a tools directory, which in a project of this age is the normal shape for a hand rolled build rather than a package manager based one. There is an editor configuration file and a git attributes file, both of which indicate the project cares about line endings and formatting in a codebase that has been through several editors.

Two files are more telling. The solution is present in the newer XML based format, alongside a settings file for the ReSharper command line tooling. Carrying the new solution format means the project is opting into the current tooling generation rather than being pinned to the older one, which is a deliberate move for a project that could easily have left the original file untouched for another decade. The ReSharper settings file next to it suggests the same intent, since that tool's file format changes between major versions and a committed settings file is a way of keeping analysis results comparable across machines and across time.

There is a reference directory and a documentation directory, and a changelog file at the root. The changelog matters more than it might for a library like this one, because with irregular releases it is often the only place a user learns what changed. Combined with the release history, a changelog that has been kept current since 2004 is one of the better signals you can get about whether a dormant looking repository is actually being maintained.

The licence situation is worth one note as well. The README states the code is free software that may be redistributed under the Apache 2.0 terms, and there is a licence file in the repository root. The licence classification the platform reports for the repository is unrecognised, which usually means the licence text does not match a pattern the classifier expects exactly, not that the terms are in doubt. For a library this old, with a licence file present and an explicit statement in the README, the terms are clear enough to rely on.

Editorial conclusion

Castle Core earns a place in a Windows enterprise codebase that still targets .NET Framework and wants a proxy generator that works on every tier it must support, which is a rarer thing in 2026 than it sounds. It is a poor fit for a greenfield .NET project with no legacy target, since the AppDomain, serialization and configuration features simply are not compiled in for you there, and it is a poor fit if you need a predictable release stream, because 5.2.1 is the only tag since March 2025 and the last commit to the default branch is dated 2026-04-14. If you adopt it, read the conditional compilation table before you design around proxies, and confirm from the package rather than the README whether the release you depend on is the one carrying your target, since the release notes and the symbol table are maintained at different speeds.

Frequently asked questions

What does Castle Core provide?

It provides common Castle Project abstractions including logging services, plus Castle DynamicProxy, a lightweight runtime proxy generator, and Castle DictionaryAdapter. API documentation is kept in a separate document under the docs directory rather than in the README.

What runtime do I need to build and test Castle Core?

Compiling needs the .NET 10 SDK. Running the whole unit test suite additionally needs .NET Framework 4.6.2 or later and the .NET 8, 9 and 10 runtimes, or you can run one target at a time with a framework flag on the test command.

Which features are only available on .NET Framework in Castle Core?

Four conditional compilation symbols are enabled only for .NET Framework 4.6.2 and disabled elsewhere: application domain support, saving the generated proxy assembly, serialization of dynamic proxies, and features that use the system configuration facility. The by-ref-like symbol is the reverse, enabled only on .NET 8 and .NET 9.

Where are Castle Core packages and debug symbols published?

The package is published to NuGet, with a preview feed hosted on the project's AppVeyor configuration for Windows and Linux. Separate symbol packages have been available in the AppVeyor build artifacts since version 4.1.0, which matters for a library that generates proxy code at runtime.

What licence is Castle Core under?

The README states the code is copyright 2004 to 2026 the Castle Project and may be redistributed under the terms of the Apache 2.0 licence, and a licence file is present at the repository root.

Official sources

  1. castleproject/Core on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/castleproject-core.svg)](https://hysenlabs.com/projects/castleproject-core)